##// END OF EJS Templates
Show revisions in diffs like CVS, based on a patch from Goffredo Baroncelli....
Thomas Arendsen Hein -
r396:8f8bb77d default
parent child Browse files
Show More
@@ -1,258 +1,258 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # Minimal support for git commands on an hg repository
4 4 #
5 5 # Copyright 2005 Chris Mason <mason@suse.com>
6 6 #
7 7 # This software may be used and distributed according to the terms
8 8 # of the GNU General Public License, incorporated herein by reference.
9 9
10 10 import time, sys, signal
11 11 from mercurial import hg, mdiff, fancyopts, commands, ui
12 12
13 def difftree(args, repo):
13 def difftree(args, ui, repo):
14 14 def __difftree(repo, files = None, node1 = None, node2 = None):
15 15 def date(c):
16 16 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
17 17
18 18 if node2:
19 19 change = repo.changelog.read(node2)
20 20 mmap2 = repo.manifest.read(change[0])
21 21 (c, a, d) = repo.diffrevs(node1, node2)
22 22 def read(f): return repo.file(f).read(mmap2[f])
23 23 date2 = date(change)
24 24 else:
25 25 date2 = time.asctime()
26 26 (c, a, d, u) = repo.diffdir(repo.root, node1)
27 27 if not node1:
28 28 node1 = repo.dirstate.parents()[0]
29 29 def read(f): return file(os.path.join(repo.root, f)).read()
30 30
31 31 change = repo.changelog.read(node1)
32 32 mmap = repo.manifest.read(change[0])
33 33 date1 = date(change)
34 34 empty = "0" * 40;
35 35
36 36 if files:
37 37 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
38 38
39 39 for f in c:
40 40 # TODO get file permissions
41 41 print ":100664 100664 %s %s %s %s" % (hg.hex(mmap[f]),
42 42 hg.hex(mmap2[f]), f, f)
43 43 for f in a:
44 44 print ":000000 100664 %s %s %s %s" % (empty, hg.hex(mmap2[f]), f, f)
45 45 for f in d:
46 46 print ":100664 000000 %s %s %s %s" % (hg.hex(mmap[f]), empty, f, f)
47 47 ##
48 48
49 49 revs = []
50 50 if args:
51 51 doptions = {}
52 52 opts = [('p', 'patch', None, 'patch'),
53 53 ('r', 'recursive', None, 'recursive')]
54 54 args = fancyopts.fancyopts(args, opts, doptions,
55 55 'hg diff-tree [options] sha1 sha1')
56 56
57 57 if len(args) < 2:
58 58 help()
59 59 sys.exit(1)
60 60 revs.append(repo.lookup(args[0]))
61 61 revs.append(repo.lookup(args[1]))
62 62 args = args[2:]
63 63 if doptions['patch']:
64 commands.dodiff(repo, "", args, *revs)
64 commands.dodiff(ui, repo, "", args, *revs)
65 65 else:
66 66 __difftree(repo, args, *revs)
67 67
68 68 def catcommit(repo, n, prefix):
69 69 nlprefix = '\n' + prefix;
70 70 changes = repo.changelog.read(n)
71 71 (p1, p2) = repo.changelog.parents(n)
72 72 (h, h1, h2) = map(hg.hex, (n, p1, p2))
73 73 (i1, i2) = map(repo.changelog.rev, (p1, p2))
74 74 print "tree %s" % (h)
75 75 if i1 != -1: print "%sparent %s" % (prefix, h1)
76 76 if i2 != -1: print "%sparent %s" % (prefix, h2)
77 77 date_ar = changes[2].split(' ')
78 78 date = int(float(date_ar[0]))
79 79 print "%sauthor <%s> %s %s" % (prefix, changes[1], date, date_ar[1])
80 80 print "%scommitter <%s> %s %s" % (prefix, changes[1], date, date_ar[1])
81 81 print prefix
82 82 if prefix != "":
83 83 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
84 84 else:
85 85 print changes[4]
86 86
87 87 def catfile(args, ui, repo):
88 88 doptions = {}
89 89 opts = [('s', 'stdin', None, 'stdin')]
90 90 args = fancyopts.fancyopts(args, opts, doptions,
91 91 'hg cat-file type sha1')
92 92
93 93 # in stdin mode, every line except the commit is prefixed with two
94 94 # spaces. This way the our caller can find the commit without magic
95 95 # strings
96 96 #
97 97 prefix = ""
98 98 if doptions['stdin']:
99 99 try:
100 100 (type, r) = raw_input().split(' ');
101 101 prefix = " "
102 102 except EOFError:
103 103 return
104 104
105 105 else:
106 106 if len(args) < 2:
107 107 help()
108 108 sys.exit(1)
109 109 type = args[0]
110 110 r = args[1]
111 111
112 112 while r:
113 113 if type != "commit":
114 114 sys.stderr.write("aborting hg cat-file only understands commits\n")
115 115 sys.exit(1);
116 116 n = repo.changelog.lookup(r)
117 117 catcommit(repo, n, prefix)
118 118 if doptions['stdin']:
119 119 try:
120 120 (type, r) = raw_input().split(' ');
121 121 except EOFError:
122 122 break
123 123 else:
124 124 break
125 125
126 126 # git rev-tree is a confusing thing. You can supply a number of
127 127 # commit sha1s on the command line, and it walks the commit history
128 128 # telling you which commits are reachable from the supplied ones via
129 129 # a bitmask based on arg position.
130 130 # you can specify a commit to stop at by starting the sha1 with ^
131 131 def revtree(args, repo, full="tree", maxnr=0):
132 132 # calculate and return the reachability bitmask for sha
133 133 def is_reachable(ar, reachable, sha):
134 134 if len(ar) == 0:
135 135 return 1
136 136 mask = 0
137 137 for i in range(len(ar)):
138 138 if sha in reachable[i]:
139 139 mask |= 1 << i
140 140
141 141 return mask
142 142
143 143 reachable = []
144 144 stop_sha1 = []
145 145 want_sha1 = []
146 146 count = 0
147 147
148 148 # figure out which commits they are asking for and which ones they
149 149 # want us to stop on
150 150 for i in range(len(args)):
151 151 if args[i].count('^'):
152 152 s = args[i].split('^')[1]
153 153 stop_sha1.append(repo.changelog.lookup(s))
154 154 want_sha1.append(s)
155 155 elif args[i] != 'HEAD':
156 156 want_sha1.append(args[i])
157 157
158 158 # calculate the graph for the supplied commits
159 159 for i in range(len(want_sha1)):
160 160 reachable.append({});
161 161 n = repo.changelog.lookup(want_sha1[i]);
162 162 visit = [n];
163 163 reachable[i][n] = 1
164 164 while visit:
165 165 n = visit.pop(0)
166 166 if n in stop_sha1:
167 167 break
168 168 for p in repo.changelog.parents(n):
169 169 if p not in reachable[i]:
170 170 reachable[i][p] = 1
171 171 visit.append(p)
172 172 if p in stop_sha1:
173 173 break
174 174
175 175 # walk the repository looking for commits that are in our
176 176 # reachability graph
177 177 for i in range(repo.changelog.count()-1, -1, -1):
178 178 n = repo.changelog.node(i)
179 179 mask = is_reachable(want_sha1, reachable, n)
180 180 if mask:
181 181 if not full:
182 182 print hg.hex(n)
183 183 elif full is "commit":
184 184 print hg.hex(n)
185 185 catcommit(repo, n, ' ')
186 186 else:
187 187 changes = repo.changelog.read(n)
188 188 (p1, p2) = repo.changelog.parents(n)
189 189 (h, h1, h2) = map(hg.hex, (n, p1, p2))
190 190 (i1, i2) = map(repo.changelog.rev, (p1, p2))
191 191
192 192 date = changes[2].split(' ')[0]
193 193 print "%s %s:%s" % (date, h, mask),
194 194 mask = is_reachable(want_sha1, reachable, p1)
195 195 if i1 != -1 and mask > 0:
196 196 print "%s:%s " % (h1, mask),
197 197 mask = is_reachable(want_sha1, reachable, p2)
198 198 if i2 != -1 and mask > 0:
199 199 print "%s:%s " % (h2, mask),
200 200 print ""
201 201 if maxnr and count >= maxnr:
202 202 break
203 203 count += 1
204 204
205 205 # git rev-list tries to order things by date, and has the ability to stop
206 206 # at a given commit without walking the whole repo. TODO add the stop
207 207 # parameter
208 208 def revlist(args, repo):
209 209 doptions = {}
210 210 opts = [('c', 'commit', None, 'commit'),
211 211 ('n', 'max-nr', 0, 'max-nr')]
212 212 args = fancyopts.fancyopts(args, opts, doptions,
213 213 'hg rev-list')
214 214 if doptions['commit']:
215 215 full = "commit"
216 216 else:
217 217 full = None
218 218 for i in range(1, len(args)):
219 219 args[i] = '^' + args[i]
220 220 revtree(args, repo, full, doptions['max-nr'])
221 221
222 222 def catchterm(*args):
223 223 raise SignalInterrupt
224 224
225 225 def help():
226 226 sys.stderr.write("commands:\n")
227 227 sys.stderr.write(" hgit cat-file [type] sha1\n")
228 228 sys.stderr.write(" hgit diff-tree [-p] [-r] sha1 sha1\n")
229 229 sys.stderr.write(" hgit rev-tree [sha1 ... [^stop sha1]]\n")
230 230 sys.stderr.write(" hgit rev-list [-c]\n")
231 231
232 232 cmd = sys.argv[1]
233 233 args = sys.argv[2:]
234 234 u = ui.ui()
235 235 signal.signal(signal.SIGTERM, catchterm)
236 236 repo = hg.repository(ui = u)
237 237
238 238 if cmd == "diff-tree":
239 difftree(args, repo)
239 difftree(args, u, repo)
240 240
241 241 elif cmd == "cat-file":
242 catfile(args, ui, repo)
242 catfile(args, u, repo)
243 243
244 244 elif cmd == "rev-tree":
245 245 revtree(args, repo)
246 246
247 247 elif cmd == "rev-list":
248 248 revlist(args, repo)
249 249
250 250 elif cmd == "help":
251 251 help()
252 252
253 253 else:
254 254 if cmd: sys.stderr.write("unknown command\n\n")
255 255 help()
256 256 sys.exit(1)
257 257
258 258 sys.exit(0)
@@ -1,772 +1,778 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os, re, sys, signal
9 9 import fancyopts, ui, hg
10 10 from demandload import *
11 11 demandload(globals(), "mdiff time hgweb traceback random signal errno")
12 12
13 13 class UnknownCommand(Exception): pass
14 14
15 15 def filterfiles(filters, files):
16 16 l = [ x for x in files if x in filters ]
17 17
18 18 for t in filters:
19 19 if t and t[-1] != os.sep: t += os.sep
20 20 l += [ x for x in files if x.startswith(t) ]
21 21 return l
22 22
23 23 def relfilter(repo, files):
24 24 if os.getcwd() != repo.root:
25 25 p = os.getcwd()[len(repo.root) + 1: ]
26 26 return filterfiles([p], files)
27 27 return files
28 28
29 29 def relpath(repo, args):
30 30 if os.getcwd() != repo.root:
31 31 p = os.getcwd()[len(repo.root) + 1: ]
32 32 return [ os.path.normpath(os.path.join(p, x)) for x in args ]
33 33 return args
34 34
35 def dodiff(repo, path, files = None, node1 = None, node2 = None):
35 def dodiff(ui, repo, path, files = None, node1 = None, node2 = None):
36 36 def date(c):
37 37 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
38 38
39 39 if node2:
40 40 change = repo.changelog.read(node2)
41 41 mmap2 = repo.manifest.read(change[0])
42 42 (c, a, d) = repo.diffrevs(node1, node2)
43 43 def read(f): return repo.file(f).read(mmap2[f])
44 44 date2 = date(change)
45 45 else:
46 46 date2 = time.asctime()
47 47 (c, a, d, u) = repo.diffdir(path, node1)
48 48 if not node1:
49 49 node1 = repo.dirstate.parents()[0]
50 50 def read(f): return file(os.path.join(repo.root, f)).read()
51 51
52 if ui.quiet:
53 r = None
54 else:
55 hexfunc = ui.verbose and hg.hex or hg.short
56 r = [hexfunc(node) for node in [node1, node2] if node]
57
52 58 change = repo.changelog.read(node1)
53 59 mmap = repo.manifest.read(change[0])
54 60 date1 = date(change)
55 61
56 62 if files:
57 63 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
58 64
59 65 for f in c:
60 66 to = None
61 67 if f in mmap:
62 68 to = repo.file(f).read(mmap[f])
63 69 tn = read(f)
64 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
70 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
65 71 for f in a:
66 72 to = None
67 73 tn = read(f)
68 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
74 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
69 75 for f in d:
70 76 to = repo.file(f).read(mmap[f])
71 77 tn = None
72 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
78 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
73 79
74 80 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
75 81 """show a single changeset or file revision"""
76 82 changelog = repo.changelog
77 83 if filelog:
78 84 log = filelog
79 85 filerev = rev
80 86 node = filenode = filelog.node(filerev)
81 87 changerev = filelog.linkrev(filenode)
82 88 changenode = changenode or changelog.node(changerev)
83 89 else:
84 90 log = changelog
85 91 changerev = rev
86 92 if changenode is None:
87 93 changenode = changelog.node(changerev)
88 94 elif not changerev:
89 95 rev = changerev = changelog.rev(changenode)
90 96 node = changenode
91 97
92 98 if ui.quiet:
93 99 ui.write("%d:%s\n" % (rev, hg.hex(node)))
94 100 return
95 101
96 102 changes = changelog.read(changenode)
97 103
98 104 parents = [(log.rev(parent), hg.hex(parent))
99 105 for parent in log.parents(node)
100 106 if ui.debugflag or parent != hg.nullid]
101 107 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
102 108 parents = []
103 109
104 110 if filelog:
105 111 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
106 112 for parent in parents:
107 113 ui.write("parent: %d:%s\n" % parent)
108 114 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
109 115 else:
110 116 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
111 117 for tag in repo.nodetags(changenode):
112 118 ui.status("tag: %s\n" % tag)
113 119 for parent in parents:
114 120 ui.write("parent: %d:%s\n" % parent)
115 121 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
116 122 hg.hex(changes[0])))
117 123 ui.status("user: %s\n" % changes[1])
118 124 ui.status("date: %s\n" % time.asctime(
119 125 time.localtime(float(changes[2].split(' ')[0]))))
120 126 ui.note("files: %s\n" % " ".join(changes[3]))
121 127 description = changes[4].strip()
122 128 if description:
123 129 if ui.verbose:
124 130 ui.status("description:\n")
125 131 ui.status(description)
126 132 ui.status("\n")
127 133 else:
128 134 ui.status("summary: %s\n" % description.splitlines()[0])
129 135 ui.status("\n")
130 136
131 137 def help(ui, cmd=None):
132 138 '''show help for a given command or all commands'''
133 139 if cmd:
134 140 try:
135 141 i = find(cmd)
136 142 ui.write("%s\n\n" % i[2])
137 143
138 144 if i[1]:
139 145 for s, l, d, c in i[1]:
140 146 opt=' '
141 147 if s: opt = opt + '-' + s + ' '
142 148 if l: opt = opt + '--' + l + ' '
143 149 if d: opt = opt + '(' + str(d) + ')'
144 150 ui.write(opt, "\n")
145 151 if c: ui.write(' %s\n' % c)
146 152 ui.write("\n")
147 153
148 154 ui.write(i[0].__doc__, "\n")
149 155 except UnknownCommand:
150 156 ui.warn("hg: unknown command %s\n" % cmd)
151 157 sys.exit(0)
152 158 else:
153 159 ui.status('hg commands:\n\n')
154 160
155 161 h = {}
156 162 for e in table.values():
157 163 f = e[0]
158 164 if f.__name__.startswith("debug"): continue
159 165 d = ""
160 166 if f.__doc__:
161 167 d = f.__doc__.splitlines(0)[0].rstrip()
162 168 h[f.__name__] = d
163 169
164 170 fns = h.keys()
165 171 fns.sort()
166 172 m = max(map(len, fns))
167 173 for f in fns:
168 174 ui.status(' %-*s %s\n' % (m, f, h[f]))
169 175
170 176 # Commands start here, listed alphabetically
171 177
172 178 def add(ui, repo, file, *files):
173 179 '''add the specified files on the next commit'''
174 180 repo.add(relpath(repo, (file,) + files))
175 181
176 182 def addremove(ui, repo, *files):
177 183 """add all new files, delete all missing files"""
178 184 if files:
179 185 files = relpath(repo, files)
180 186 d = []
181 187 u = []
182 188 for f in files:
183 189 p = repo.wjoin(f)
184 190 s = repo.dirstate.state(f)
185 191 isfile = os.path.isfile(p)
186 192 if s != 'r' and not isfile:
187 193 d.append(f)
188 194 elif s not in 'nmai' and isfile:
189 195 u.append(f)
190 196 else:
191 197 (c, a, d, u) = repo.diffdir(repo.root)
192 198 repo.add(u)
193 199 repo.remove(d)
194 200
195 201 def annotate(u, repo, file, *files, **ops):
196 202 """show changeset information per file line"""
197 203 def getnode(rev):
198 204 return hg.short(repo.changelog.node(rev))
199 205
200 206 def getname(rev):
201 207 try:
202 208 return bcache[rev]
203 209 except KeyError:
204 210 cl = repo.changelog.read(repo.changelog.node(rev))
205 211 name = cl[1]
206 212 f = name.find('@')
207 213 if f >= 0:
208 214 name = name[:f]
209 215 bcache[rev] = name
210 216 return name
211 217
212 218 bcache = {}
213 219 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
214 220 if not ops['user'] and not ops['changeset']:
215 221 ops['number'] = 1
216 222
217 223 node = repo.dirstate.parents()[0]
218 224 if ops['revision']:
219 225 node = repo.changelog.lookup(ops['revision'])
220 226 change = repo.changelog.read(node)
221 227 mmap = repo.manifest.read(change[0])
222 228 maxuserlen = 0
223 229 maxchangelen = 0
224 230 for f in relpath(repo, (file,) + files):
225 231 lines = repo.file(f).annotate(mmap[f])
226 232 pieces = []
227 233
228 234 for o, f in opmap:
229 235 if ops[o]:
230 236 l = [ f(n) for n,t in lines ]
231 237 m = max(map(len, l))
232 238 pieces.append([ "%*s" % (m, x) for x in l])
233 239
234 240 for p,l in zip(zip(*pieces), lines):
235 241 u.write(" ".join(p) + ": " + l[1])
236 242
237 243 def cat(ui, repo, file, rev = []):
238 244 """output the latest or given revision of a file"""
239 245 r = repo.file(relpath(repo, [file])[0])
240 246 n = r.tip()
241 247 if rev: n = r.lookup(rev)
242 248 sys.stdout.write(r.read(n))
243 249
244 250 def commit(ui, repo, *files, **opts):
245 251 """commit the specified files or all outstanding changes"""
246 252 text = opts['text']
247 253 if not text and opts['logfile']:
248 254 try: text = open(opts['logfile']).read()
249 255 except IOError: pass
250 256
251 257 if opts['addremove']:
252 258 addremove(ui, repo, *files)
253 259 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
254 260
255 261 def copy(ui, repo, source, dest):
256 262 """mark a file as copied or renamed for the next commit"""
257 263 return repo.copy(*relpath(repo, (source, dest)))
258 264
259 265 def debugaddchangegroup(ui, repo):
260 266 data = sys.stdin.read()
261 267 repo.addchangegroup(data)
262 268
263 269 def debugchangegroup(ui, repo, roots):
264 270 newer = repo.newer(map(repo.lookup, roots))
265 271 for chunk in repo.changegroup(newer):
266 272 sys.stdout.write(chunk)
267 273
268 274 def debugindex(ui, file):
269 275 r = hg.revlog(open, file, "")
270 276 print " rev offset length base linkrev"+\
271 277 " p1 p2 nodeid"
272 278 for i in range(r.count()):
273 279 e = r.index[i]
274 280 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
275 281 i, e[0], e[1], e[2], e[3],
276 282 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
277 283
278 284 def debugindexdot(ui, file):
279 285 r = hg.revlog(open, file, "")
280 286 print "digraph G {"
281 287 for i in range(r.count()):
282 288 e = r.index[i]
283 289 print "\t%d -> %d" % (r.rev(e[4]), i)
284 290 if e[5] != hg.nullid:
285 291 print "\t%d -> %d" % (r.rev(e[5]), i)
286 292 print "}"
287 293
288 294 def diff(ui, repo, *files, **opts):
289 295 """diff working directory (or selected files)"""
290 296 revs = []
291 297 if opts['rev']:
292 298 revs = map(lambda x: repo.lookup(x), opts['rev'])
293
299
294 300 if len(revs) > 2:
295 301 self.ui.warn("too many revisions to diff\n")
296 302 sys.exit(1)
297 303
298 304 if files:
299 305 files = relpath(repo, files)
300 306 else:
301 307 files = relpath(repo, [""])
302 308
303 dodiff(repo, os.getcwd(), files, *revs)
309 dodiff(ui, repo, os.getcwd(), files, *revs)
304 310
305 311 def export(ui, repo, changeset):
306 312 """dump the changeset header and diffs for a revision"""
307 313 node = repo.lookup(changeset)
308 314 prev, other = repo.changelog.parents(node)
309 315 change = repo.changelog.read(node)
310 316 print "# HG changeset patch"
311 317 print "# User %s" % change[1]
312 318 print "# Node ID %s" % hg.hex(node)
313 319 print "# Parent %s" % hg.hex(prev)
314 320 print
315 321 if other != hg.nullid:
316 322 print "# Parent %s" % hg.hex(other)
317 323 print change[4].rstrip()
318 324 print
319
320 dodiff(repo, "", None, prev, node)
325
326 dodiff(ui, repo, "", None, prev, node)
321 327
322 328 def forget(ui, repo, file, *files):
323 329 """don't add the specified files on the next commit"""
324 330 repo.forget(relpath(repo, (file,) + files))
325 331
326 332 def heads(ui, repo):
327 333 """show current repository heads"""
328 334 for n in repo.changelog.heads():
329 335 show_changeset(ui, repo, changenode=n)
330 336
331 337 def history(ui, repo):
332 338 """show the changelog history"""
333 339 for i in range(repo.changelog.count() - 1, -1, -1):
334 340 show_changeset(ui, repo, rev=i)
335 341
336 342 def identify(ui, repo):
337 343 """print information about the working copy"""
338 344 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
339 345 if not parents:
340 346 ui.write("unknown\n")
341 347 return
342 348
343 349 hexfunc = ui.verbose and hg.hex or hg.short
344 350 (c, a, d, u) = repo.diffdir(repo.root)
345 351 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
346 352 (c or a or d) and "+" or "")]
347 353
348 354 if not ui.quiet:
349 355 # multiple tags for a single parent separated by '/'
350 356 parenttags = ['/'.join(tags)
351 357 for tags in map(repo.nodetags, parents) if tags]
352 358 # tags for multiple parents separated by ' + '
353 359 output.append(' + '.join(parenttags))
354 360
355 361 ui.write("%s\n" % ' '.join(output))
356 362
357 363 def init(ui, source=None, **opts):
358 364 """create a new repository or copy an existing one"""
359 365
360 366 if source:
361 367 paths = {}
362 368 for name, path in ui.configitems("paths"):
363 369 paths[name] = path
364 370
365 371 if source in paths: source = paths[source]
366 372
367 373 link = 0
368 374 if not source.startswith("http://"):
369 375 d1 = os.stat(os.getcwd()).st_dev
370 376 d2 = os.stat(source).st_dev
371 377 if d1 == d2: link = 1
372 378
373 379 if link:
374 380 ui.debug("copying by hardlink\n")
375 381 os.system("cp -al %s/.hg .hg" % source)
376 382 try:
377 383 os.remove(".hg/dirstate")
378 384 except: pass
379 385
380 386 repo = hg.repository(ui, ".")
381 387
382 388 else:
383 389 repo = hg.repository(ui, ".", create=1)
384 390 other = hg.repository(ui, source)
385 391 cg = repo.getchangegroup(other)
386 392 repo.addchangegroup(cg)
387 393
388 394 f = repo.opener("hgrc", "w")
389 395 f.write("[paths]\n")
390 396 f.write("default = %s\n" % source)
391 397
392 398 if opts['update']:
393 399 update(ui, repo)
394 400 else:
395 401 repo = hg.repository(ui, ".", create=1)
396 402
397 403 def log(ui, repo, f):
398 404 """show the revision history of a single file"""
399 405 f = relpath(repo, [f])[0]
400 406
401 407 r = repo.file(f)
402 408 for i in range(r.count() - 1, -1, -1):
403 409 show_changeset(ui, repo, filelog=r, rev=i)
404 410
405 411 def manifest(ui, repo, rev = []):
406 412 """output the latest or given revision of the project manifest"""
407 413 n = repo.manifest.tip()
408 414 if rev:
409 415 n = repo.manifest.lookup(rev)
410 416 m = repo.manifest.read(n)
411 417 mf = repo.manifest.readflags(n)
412 418 files = m.keys()
413 419 files.sort()
414 420
415 421 for f in files:
416 422 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
417 423
418 424 def parents(ui, repo, node = None):
419 425 '''show the parents of the current working dir'''
420 426 if node:
421 427 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
422 428 else:
423 429 p = repo.dirstate.parents()
424 430
425 431 for n in p:
426 432 if n != hg.nullid:
427 433 show_changeset(ui, repo, changenode=n)
428 434
429 435 def patch(ui, repo, patch1, *patches, **opts):
430 436 """import an ordered set of patches"""
431 437 try:
432 438 import psyco
433 439 psyco.full()
434 440 except:
435 441 pass
436 442
437 443 patches = (patch1,) + patches
438 444
439 445 d = opts["base"]
440 446 strip = opts["strip"]
441 447 quiet = opts["quiet"] and "> /dev/null" or ""
442 448
443 449 for patch in patches:
444 450 ui.status("applying %s\n" % patch)
445 451 pf = os.path.join(d, patch)
446 452
447 453 text = ""
448 454 for l in file(pf):
449 455 if l[:4] == "--- ": break
450 456 text += l
451 457
452 458 # make sure text isn't empty
453 459 if not text: text = "imported patch %s\n" % patch
454 460
455 461 f = os.popen("patch -p%d < %s" % (strip, pf))
456 462 files = []
457 463 for l in f.read().splitlines():
458 464 l.rstrip('\r\n');
459 465 if not quiet:
460 466 print l
461 467 if l[:14] == 'patching file ':
462 468 files.append(l[14:])
463 469 f.close()
464 470
465 471 if len(files) > 0:
466 472 addremove(ui, repo, *files)
467 473 repo.commit(files, text)
468 474
469 475 def pull(ui, repo, source="default"):
470 476 """pull changes from the specified source"""
471 477 paths = {}
472 478 for name, path in ui.configitems("paths"):
473 479 paths[name] = path
474 480
475 481 if source in paths: source = paths[source]
476 482
477 483 other = hg.repository(ui, source)
478 484 cg = repo.getchangegroup(other)
479 485 repo.addchangegroup(cg)
480 486
481 487 def push(ui, repo, dest="default-push"):
482 488 """push changes to the specified destination"""
483 489 paths = {}
484 490 for name, path in ui.configitems("paths"):
485 491 paths[name] = path
486 492
487 493 if dest in paths: dest = paths[dest]
488 494
489 495 if not dest.startswith("ssh://"):
490 496 ui.warn("abort: can only push to ssh:// destinations currently\n")
491 497 return 1
492 498
493 499 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
494 500 if not m:
495 501 ui.warn("abort: couldn't parse destination %s\n" % dest)
496 502 return 1
497 503
498 504 user, host, port, path = map(m.group, (2, 3, 5, 7))
499 505 host = user and ("%s@%s" % (user, host)) or host
500 506 port = port and (" -p %s") % port or ""
501 507 path = path or ""
502 508
503 509 sport = random.randrange(30000, 60000)
504 510 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
505 511 cmd = cmd % (host, port, sport+1, sport, path, sport+1)
506 512
507 513 child = os.fork()
508 514 if not child:
509 515 sys.stdout = file("/dev/null", "w")
510 516 sys.stderr = sys.stdout
511 517 hgweb.server(repo.root, "pull", "", "localhost", sport)
512 518 else:
513 519 r = os.system(cmd)
514 520 os.kill(child, signal.SIGTERM)
515 521 return r
516 522
517 523 def rawcommit(ui, repo, flist, **rc):
518 524 "raw commit interface"
519 525
520 526 text = rc['text']
521 527 if not text and rc['logfile']:
522 528 try: text = open(rc['logfile']).read()
523 529 except IOError: pass
524 530 if not text and not rc['logfile']:
525 531 print "missing commit text"
526 532 return 1
527 533
528 534 files = relpath(repo, flist)
529 535 if rc['files']:
530 536 files += open(rc['files']).read().splitlines()
531 537
532 538 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
533 539
534 540 def recover(ui, repo):
535 541 """roll back an interrupted transaction"""
536 542 repo.recover()
537 543
538 544 def remove(ui, repo, file, *files):
539 545 """remove the specified files on the next commit"""
540 546 repo.remove(relpath(repo, (file,) + files))
541 547
542 548 def serve(ui, repo, **opts):
543 549 """export the repository via HTTP"""
544 550 hgweb.server(repo.root, opts["name"], opts["templates"],
545 551 opts["address"], opts["port"])
546 552
547 553 def status(ui, repo):
548 554 '''show changed files in the working directory
549 555
550 556 C = changed
551 557 A = added
552 558 R = removed
553 559 ? = not tracked'''
554 560
555 561 (c, a, d, u) = repo.diffdir(os.getcwd())
556 562 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
557 563
558 564 for f in c: print "C", f
559 565 for f in a: print "A", f
560 566 for f in d: print "R", f
561 567 for f in u: print "?", f
562 568
563 569 def tags(ui, repo):
564 570 """list repository tags"""
565 571
566 572 l = repo.tagslist()
567 573 l.reverse()
568 574 for t,n in l:
569 575 try:
570 576 r = repo.changelog.rev(n)
571 577 except KeyError:
572 578 r = "?"
573 579 print "%-30s %5d:%s" % (t, repo.changelog.rev(n), hg.hex(n))
574 580
575 581 def tip(ui, repo):
576 582 """show the tip revision"""
577 583 n = repo.changelog.tip()
578 584 show_changeset(ui, repo, changenode=n)
579 585
580 586 def undo(ui, repo):
581 587 """undo the last transaction"""
582 588 repo.undo()
583 589
584 590 def update(ui, repo, node=None, merge=False, clean=False):
585 591 '''update or merge working directory
586 592
587 593 If there are no outstanding changes in the working directory and
588 594 there is a linear relationship between the current version and the
589 595 requested version, the result is the requested version.
590 596
591 597 Otherwise the result is a merge between the contents of the
592 598 current working directory and the requested version. Files that
593 599 changed between either parent are marked as changed for the next
594 600 commit and a commit must be performed before any further updates
595 601 are allowed.
596 602 '''
597 603 node = node and repo.lookup(node) or repo.changelog.tip()
598 604 return repo.update(node, allow=merge, force=clean)
599 605
600 606 def verify(ui, repo):
601 607 """verify the integrity of the repository"""
602 608 return repo.verify()
603 609
604 610 # Command options and aliases are listed here, alphabetically
605 611
606 612 table = {
607 613 "add": (add, [], "hg add [files]"),
608 614 "addremove": (addremove, [], "hg addremove [files]"),
609 615 "ann|annotate": (annotate,
610 616 [('r', 'revision', '', 'revision'),
611 617 ('u', 'user', None, 'show user'),
612 618 ('n', 'number', None, 'show revision number'),
613 619 ('c', 'changeset', None, 'show changeset')],
614 620 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
615 621 "cat|dump": (cat, [], 'hg cat <file> [rev]'),
616 622 "commit|ci": (commit,
617 623 [('t', 'text', "", 'commit text'),
618 624 ('A', 'addremove', None, 'run add/remove during commit'),
619 625 ('l', 'logfile', "", 'commit text file'),
620 626 ('d', 'date', "", 'data'),
621 627 ('u', 'user', "", 'user')],
622 628 'hg commit [files]'),
623 629 "copy": (copy, [], 'hg copy <source> <dest>'),
624 630 "debugaddchangegroup": (debugaddchangegroup, [], 'debugaddchangegroup'),
625 631 "debugchangegroup": (debugchangegroup, [], 'debugchangegroup [roots]'),
626 632 "debugindex": (debugindex, [], 'debugindex <file>'),
627 633 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
628 634 "diff": (diff, [('r', 'rev', [], 'revision')],
629 635 'hg diff [-r A] [-r B] [files]'),
630 636 "export": (export, [], "hg export <changeset>"),
631 637 "forget": (forget, [], "hg forget [files]"),
632 638 "heads": (heads, [], 'hg heads'),
633 639 "history": (history, [], 'hg history'),
634 640 "help": (help, [], 'hg help [command]'),
635 641 "identify|id": (identify, [], 'hg identify'),
636 642 "init": (init, [('u', 'update', None, 'update after init')],
637 643 'hg init [options] [url]'),
638 644 "log": (log, [], 'hg log <file>'),
639 645 "manifest|dumpmanifest": (manifest, [], 'hg manifest [rev]'),
640 646 "parents": (parents, [], 'hg parents [node]'),
641 647 "patch|import": (patch,
642 648 [('p', 'strip', 1, 'path strip'),
643 649 ('b', 'base', "", 'base path'),
644 650 ('q', 'quiet', "", 'silence diff')],
645 651 "hg import [options] patches"),
646 652 "pull|merge": (pull, [], 'hg pull [source]'),
647 653 "push": (push, [], 'hg push <destination>'),
648 654 "rawcommit": (rawcommit,
649 655 [('p', 'parent', [], 'parent'),
650 656 ('d', 'date', "", 'data'),
651 657 ('u', 'user', "", 'user'),
652 658 ('F', 'files', "", 'file list'),
653 659 ('t', 'text', "", 'commit text'),
654 660 ('l', 'logfile', "", 'commit text file')],
655 661 'hg rawcommit [options] [files]'),
656 662 "recover": (recover, [], "hg recover"),
657 663 "remove": (remove, [], "hg remove [files]"),
658 664 "serve": (serve, [('p', 'port', 8000, 'listen port'),
659 665 ('a', 'address', '', 'interface address'),
660 666 ('n', 'name', os.getcwd(), 'repository name'),
661 667 ('t', 'templates', "", 'template map')],
662 668 "hg serve [options]"),
663 669 "status": (status, [], 'hg status'),
664 670 "tags": (tags, [], 'hg tags'),
665 671 "tip": (tip, [], 'hg tip'),
666 672 "undo": (undo, [], 'hg undo'),
667 673 "update|up|checkout|co|resolve": (update,
668 674 [('m', 'merge', None,
669 675 'allow merging of conflicts'),
670 676 ('C', 'clean', None,
671 677 'overwrite locally modified files')],
672 678 'hg update [options] [node]'),
673 679 "verify": (verify, [], 'hg verify'),
674 680 }
675 681
676 682 norepo = "init branch help debugindex debugindexdot"
677 683
678 684 def find(cmd):
679 685 i = None
680 686 for e in table.keys():
681 687 if re.match("(%s)$" % e, cmd):
682 688 return table[e]
683 689
684 690 raise UnknownCommand(cmd)
685 691
686 692 class SignalInterrupt(Exception): pass
687 693
688 694 def catchterm(*args):
689 695 raise SignalInterrupt
690 696
691 697 def run():
692 698 sys.exit(dispatch(sys.argv[1:]))
693 699
694 700 def dispatch(args):
695 701 options = {}
696 702 opts = [('v', 'verbose', None, 'verbose'),
697 703 ('d', 'debug', None, 'debug'),
698 704 ('q', 'quiet', None, 'quiet'),
699 705 ('p', 'profile', None, 'profile'),
700 706 ('y', 'noninteractive', None, 'run non-interactively'),
701 707 ]
702 708
703 709 args = fancyopts.fancyopts(args, opts, options,
704 710 'hg [options] <command> [options] [files]')
705 711
706 712 if not args:
707 713 cmd = "help"
708 714 else:
709 715 cmd, args = args[0], args[1:]
710 716
711 717 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
712 718 not options["noninteractive"])
713 719
714 720 try:
715 721 i = find(cmd)
716 722 except UnknownCommand:
717 723 u.warn("hg: unknown command '%s'\n" % cmd)
718 724 help(u)
719 725 sys.exit(1)
720 726
721 727 signal.signal(signal.SIGTERM, catchterm)
722 728
723 729 cmdoptions = {}
724 730 try:
725 731 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
726 732 except fancyopts.getopt.GetoptError, inst:
727 733 u.warn("hg %s: %s\n" % (cmd, inst))
728 734 help(u, cmd)
729 735 sys.exit(-1)
730 736
731 737 if cmd not in norepo.split():
732 738 repo = hg.repository(ui = u)
733 739 d = lambda: i[0](u, repo, *args, **cmdoptions)
734 740 else:
735 741 d = lambda: i[0](u, *args, **cmdoptions)
736 742
737 743 try:
738 744 if options['profile']:
739 745 import hotshot, hotshot.stats
740 746 prof = hotshot.Profile("hg.prof")
741 747 r = prof.runcall(d)
742 748 prof.close()
743 749 stats = hotshot.stats.load("hg.prof")
744 750 stats.strip_dirs()
745 751 stats.sort_stats('time', 'calls')
746 752 stats.print_stats(40)
747 753 return r
748 754 else:
749 755 return d()
750 756 except SignalInterrupt:
751 757 u.warn("killed!\n")
752 758 except KeyboardInterrupt:
753 759 u.warn("interrupted!\n")
754 760 except IOError, inst:
755 761 if hasattr(inst, "code"):
756 762 u.warn("abort: %s\n" % inst)
757 763 elif hasattr(inst, "reason"):
758 764 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
759 765 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
760 766 u.warn("broken pipe\n")
761 767 else:
762 768 raise
763 769 except TypeError, inst:
764 770 # was this an argument error?
765 771 tb = traceback.extract_tb(sys.exc_info()[2])
766 772 if len(tb) > 2: # no
767 773 raise
768 774 u.debug(inst, "\n")
769 775 u.warn("%s: invalid arguments\n" % i[0].__name__)
770 776 help(u, cmd)
771 777 sys.exit(-1)
772 778
@@ -1,115 +1,120 b''
1 1 # mdiff.py - diff and patch routines for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import difflib, struct
9 9 from mercurial.mpatch import *
10 10
11 def unidiff(a, ad, b, bd, fn):
11 def unidiff(a, ad, b, bd, fn, r=None):
12
12 13 if not a and not b: return ""
13 14
14 15 if a == None:
15 16 b = b.splitlines(1)
16 17 l1 = "--- %s\t%s\n" % ("/dev/null", ad)
17 18 l2 = "+++ %s\t%s\n" % ("b/" + fn, bd)
18 19 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
19 20 l = [l1, l2, l3] + ["+" + e for e in b]
20 21 elif b == None:
21 22 a = a.splitlines(1)
22 23 l1 = "--- %s\t%s\n" % ("a/" + fn, ad)
23 24 l2 = "+++ %s\t%s\n" % ("/dev/null", bd)
24 25 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
25 26 l = [l1, l2, l3] + ["-" + e for e in a]
26 27 else:
27 28 a = a.splitlines(1)
28 29 b = b.splitlines(1)
29 30 l = list(difflib.unified_diff(a, b, "a/" + fn, "b/" + fn))
30 31 if not l: return ""
31 32 # difflib uses a space, rather than a tab
32 33 l[0] = l[0][:-2] + "\t" + ad + "\n"
33 34 l[1] = l[1][:-2] + "\t" + bd + "\n"
34 35
35 36 for ln in xrange(len(l)):
36 37 if l[ln][-1] != '\n':
37 38 l[ln] += "\n\ No newline at end of file\n"
38 39
40 if r:
41 l.insert(0, "diff %s %s\n" %
42 (' '.join(["-r %s" % rev for rev in r]), fn))
43
39 44 return "".join(l)
40 45
41 46 def textdiff(a, b):
42 47 return diff(a.splitlines(1), b.splitlines(1))
43 48
44 49 def sortdiff(a, b):
45 50 la = lb = 0
46 51 lena = len(a)
47 52 lenb = len(b)
48 53
49 54 while 1:
50 55 am, bm, = la, lb
51 56
52 57 # walk over matching lines
53 58 while lb < lenb and la < lena and a[la] == b[lb] :
54 59 la += 1
55 60 lb += 1
56 61
57 62 if la > am:
58 63 yield (am, bm, la - am) # return a match
59 64
60 65 # skip mismatched lines from b
61 66 while la < lena and lb < lenb and b[lb] < a[la]:
62 67 lb += 1
63 68
64 69 if lb >= lenb:
65 70 break
66 71
67 72 # skip mismatched lines from a
68 73 while la < lena and lb < lenb and b[lb] > a[la]:
69 74 la += 1
70 75
71 76 if la >= lena:
72 77 break
73 78
74 79 yield (lena, lenb, 0)
75 80
76 81 def diff(a, b, sorted=0):
77 82 if not a:
78 83 s = "".join(b)
79 84 return s and (struct.pack(">lll", 0, 0, len(s)) + s)
80 85
81 86 bin = []
82 87 p = [0]
83 88 for i in a: p.append(p[-1] + len(i))
84 89
85 90 if sorted:
86 91 try:
87 92 d = sortdiff(a, b)
88 93 except:
89 94 print a, b
90 95 raise
91 96 else:
92 97 d = difflib.SequenceMatcher(None, a, b).get_matching_blocks()
93 98 la = 0
94 99 lb = 0
95 100 for am, bm, size in d:
96 101 s = "".join(b[lb:bm])
97 102 if am > la or s:
98 103 bin.append(struct.pack(">lll", p[la], p[am], len(s)) + s)
99 104 la = am + size
100 105 lb = bm + size
101 106
102 107 return "".join(bin)
103 108
104 109 def patchtext(bin):
105 110 pos = 0
106 111 t = []
107 112 while pos < len(bin):
108 113 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
109 114 pos += 12
110 115 t.append(bin[pos:pos + l])
111 116 pos += l
112 117 return "".join(t)
113 118
114 119 def patch(a, bin):
115 120 return patches(a, [bin])
@@ -1,72 +1,74 b''
1 1 + mkdir r1
2 2 + cd r1
3 3 + hg init
4 4 + echo a
5 5 + hg addremove
6 6 + hg commit -t 1 -u test -d '0 0'
7 7 + cd ..
8 8 + mkdir r2
9 9 + cd r2
10 10 + hg init ../r1
11 11 + hg up
12 12 + echo abc
13 13 + hg diff
14 14 + sed 's/\(\(---\|+++\).*\)\t.*/\1/'
15 diff -r c19d34741b0a a
15 16 --- a/a
16 17 +++ b/a
17 18 @@ -1,1 +1,1 @@
18 19 -a
19 20 +abc
20 21 + cd ../r1
21 22 + echo b
22 23 + echo a2
23 24 + hg addremove
24 25 + hg commit -t 2 -u test -d '0 0'
25 26 + cd ../r2
26 27 + hg -q pull ../r1
27 28 + hg status
28 29 C a
29 30 + hg -d up
30 31 resolving manifests
31 32 ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
32 33 a versions differ, resolve
33 34 remote created b
34 35 getting b
35 36 merging a
36 37 resolving a
37 38 file a: other d730145abbf9 ancestor b789fdd96dc2
38 39 + hg -d up -m
39 40 resolving manifests
40 41 ancestor 1165e8bd193e local 1165e8bd193e remote 1165e8bd193e
41 42 + hg parents
42 43 changeset: 1:1e71731e6fbb5b35fae293120dea6964371c13c6
43 44 tag: tip
44 45 user: test
45 46 date: Thu Jan 1 00:00:00 1970
46 47 summary: 2
47 48
48 49 + hg -v history
49 50 changeset: 1:1e71731e6fbb5b35fae293120dea6964371c13c6
50 51 tag: tip
51 52 manifest: 1:1165e8bd193e17ad7d321d846fcf27ff3f412758
52 53 user: test
53 54 date: Thu Jan 1 00:00:00 1970
54 55 files: a b
55 56 description:
56 57 2
57 58
58 59 changeset: 0:c19d34741b0a4ced8e4ba74bb834597d5193851e
59 60 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
60 61 user: test
61 62 date: Thu Jan 1 00:00:00 1970
62 63 files: a
63 64 description:
64 65 1
65 66
66 67 + hg diff
67 68 + sed 's/\(\(---\|+++\).*\)\t.*/\1/'
69 diff -r 1e71731e6fbb a
68 70 --- a/a
69 71 +++ b/a
70 72 @@ -1,1 +1,1 @@
71 73 -a2
72 74 +abc
General Comments 0
You need to be logged in to leave comments. Login now