Move the long option name enum from cl.h into main.c, because it is
[svn.git] / tools / examples / svnlook.py
blobc4fb877d75c09d0f2f7e185650cb4c59752b0dc8
1 #!/usr/bin/env python
3 # svnlook.py : a Python-based replacement for svnlook
5 ######################################################################
7 # Copyright (c) 2000-2004 CollabNet. All rights reserved.
9 # This software is licensed as described in the file COPYING, which
10 # you should have received as part of this distribution. The terms
11 # are also available at http://subversion.tigris.org/license-1.html.
12 # If newer versions of this license are posted there, you may use a
13 # newer version instead, at your option.
15 ######################################################################
18 import sys
19 import time
20 import os
22 from svn import core, fs, delta, repos
24 class SVNLook:
25 def __init__(self, path, cmd, rev, txn):
26 path = core.svn_path_canonicalize(path)
27 repos_ptr = repos.open(path)
28 self.fs_ptr = repos.fs(repos_ptr)
30 if txn:
31 self.txn_ptr = fs.open_txn(self.fs_ptr, txn)
32 else:
33 self.txn_ptr = None
34 if rev is None:
35 rev = fs.youngest_rev(self.fs_ptr)
36 self.rev = rev
38 getattr(self, 'cmd_' + cmd)()
40 def cmd_default(self):
41 self.cmd_info()
42 self.cmd_tree()
44 def cmd_author(self):
45 # get the author property, or empty string if the property is not present
46 author = self._get_property(core.SVN_PROP_REVISION_AUTHOR) or ''
47 print author
49 def cmd_changed(self):
50 self._print_tree(ChangedEditor, pass_root=1)
52 def cmd_date(self):
53 if self.txn_ptr:
54 print
55 else:
56 date = self._get_property(core.SVN_PROP_REVISION_DATE)
57 if date:
58 aprtime = core.svn_time_from_cstring(date)
59 # ### convert to a time_t; this requires intimate knowledge of
60 # ### the apr_time_t type
61 secs = aprtime / 1000000 # aprtime is microseconds; make seconds
63 # assume secs in local TZ, convert to tuple, and format
64 ### we don't really know the TZ, do we?
65 print time.strftime('%Y-%m-%d %H:%M', time.localtime(secs))
66 else:
67 print
69 def cmd_diff(self):
70 self._print_tree(DiffEditor, pass_root=1)
72 def cmd_dirs_changed(self):
73 self._print_tree(DirsChangedEditor)
75 def cmd_ids(self):
76 self._print_tree(Editor, base_rev=0, pass_root=1)
78 def cmd_info(self):
79 self.cmd_author()
80 self.cmd_date()
81 self.cmd_log(1)
83 def cmd_log(self, print_size=0):
84 # get the log property, or empty string if the property is not present
85 log = self._get_property(core.SVN_PROP_REVISION_LOG) or ''
86 if print_size:
87 print len(log)
88 print log
90 def cmd_tree(self):
91 self._print_tree(Editor, base_rev=0)
93 def _get_property(self, name):
94 if self.txn_ptr:
95 return fs.txn_prop(self.txn_ptr, name)
96 return fs.revision_prop(self.fs_ptr, self.rev, name)
98 def _print_tree(self, e_factory, base_rev=None, pass_root=0):
99 if base_rev is None:
100 # a specific base rev was not provided. use the transaction base,
101 # or the previous revision
102 if self.txn_ptr:
103 base_rev = fs.txn_base_revision(self.txn_ptr)
104 else:
105 base_rev = self.rev - 1
107 # get the current root
108 if self.txn_ptr:
109 root = fs.txn_root(self.txn_ptr)
110 else:
111 root = fs.revision_root(self.fs_ptr, self.rev)
113 # the base of the comparison
114 base_root = fs.revision_root(self.fs_ptr, base_rev)
116 if pass_root:
117 editor = e_factory(root, base_root)
118 else:
119 editor = e_factory()
121 # construct the editor for printing these things out
122 e_ptr, e_baton = delta.make_editor(editor)
124 # compute the delta, printing as we go
125 def authz_cb(root, path, pool):
126 return 1
127 repos.dir_delta(base_root, '', '', root, '',
128 e_ptr, e_baton, authz_cb, 0, 1, 0, 0)
131 class Editor(delta.Editor):
132 def __init__(self, root=None, base_root=None):
133 self.root = root
134 # base_root ignored
136 self.indent = ''
138 def open_root(self, base_revision, dir_pool):
139 print '/' + self._get_id('/')
140 self.indent = self.indent + ' ' # indent one space
142 def add_directory(self, path, *args):
143 id = self._get_id(path)
144 print self.indent + _basename(path) + '/' + id
145 self.indent = self.indent + ' ' # indent one space
147 # we cheat. one method implementation for two entry points.
148 open_directory = add_directory
150 def close_directory(self, baton):
151 # note: if indents are being performed, this slice just returns
152 # another empty string.
153 self.indent = self.indent[:-1]
155 def add_file(self, path, *args):
156 id = self._get_id(path)
157 print self.indent + _basename(path) + id
159 # we cheat. one method implementation for two entry points.
160 open_file = add_file
162 def _get_id(self, path):
163 if self.root:
164 id = fs.node_id(self.root, path)
165 return ' <%s>' % fs.unparse_id(id)
166 return ''
169 class DirsChangedEditor(delta.Editor):
170 def open_root(self, base_revision, dir_pool):
171 return [ 1, '' ]
173 def delete_entry(self, path, revision, parent_baton, pool):
174 self._dir_changed(parent_baton)
176 def add_directory(self, path, parent_baton,
177 copyfrom_path, copyfrom_revision, dir_pool):
178 self._dir_changed(parent_baton)
179 return [ 1, path ]
181 def open_directory(self, path, parent_baton, base_revision, dir_pool):
182 return [ 1, path ]
184 def change_dir_prop(self, dir_baton, name, value, pool):
185 self._dir_changed(dir_baton)
187 def add_file(self, path, parent_baton,
188 copyfrom_path, copyfrom_revision, file_pool):
189 self._dir_changed(parent_baton)
191 def open_file(self, path, parent_baton, base_revision, file_pool):
192 # some kind of change is going to happen
193 self._dir_changed(parent_baton)
195 def _dir_changed(self, baton):
196 if baton[0]:
197 # the directory hasn't been printed yet. do it.
198 print baton[1] + '/'
199 baton[0] = 0
202 class ChangedEditor(delta.Editor):
203 def __init__(self, root, base_root):
204 self.root = root
205 self.base_root = base_root
207 def open_root(self, base_revision, dir_pool):
208 return [ 1, '' ]
210 def delete_entry(self, path, revision, parent_baton, pool):
211 ### need more logic to detect 'replace'
212 if fs.is_dir(self.base_root, '/' + path):
213 print 'D ' + path + '/'
214 else:
215 print 'D ' + path
217 def add_directory(self, path, parent_baton,
218 copyfrom_path, copyfrom_revision, dir_pool):
219 print 'A ' + path + '/'
220 return [ 0, path ]
222 def open_directory(self, path, parent_baton, base_revision, dir_pool):
223 return [ 1, path ]
225 def change_dir_prop(self, dir_baton, name, value, pool):
226 if dir_baton[0]:
227 # the directory hasn't been printed yet. do it.
228 print '_U ' + dir_baton[1] + '/'
229 dir_baton[0] = 0
231 def add_file(self, path, parent_baton,
232 copyfrom_path, copyfrom_revision, file_pool):
233 print 'A ' + path
234 return [ '_', ' ', None ]
236 def open_file(self, path, parent_baton, base_revision, file_pool):
237 return [ '_', ' ', path ]
239 def apply_textdelta(self, file_baton, base_checksum):
240 file_baton[0] = 'U'
242 # no handler
243 return None
245 def change_file_prop(self, file_baton, name, value, pool):
246 file_baton[1] = 'U'
248 def close_file(self, file_baton, text_checksum):
249 text_mod, prop_mod, path = file_baton
250 # test the path. it will be None if we added this file.
251 if path:
252 status = text_mod + prop_mod
253 # was there some kind of change?
254 if status != '_ ':
255 print status + ' ' + path
258 class DiffEditor(delta.Editor):
259 def __init__(self, root, base_root):
260 self.root = root
261 self.base_root = base_root
263 def _do_diff(self, base_path, path):
264 if base_path is None:
265 print "Added: " + path
266 label = path
267 elif path is None:
268 print "Removed: " + path
269 label = base_path
270 else:
271 print "Modified: " + path
272 label = path
273 print "===============================================================" + \
274 "==============="
275 args = []
276 args.append("-L")
277 args.append(label + "\t(original)")
278 args.append("-L")
279 args.append(label + "\t(new)")
280 args.append("-u")
281 differ = fs.FileDiff(self.base_root, base_path, self.root,
282 path, diffoptions=args)
283 pobj = differ.get_pipe()
284 while 1:
285 line = pobj.readline()
286 if not line:
287 break
288 print line,
289 print ""
291 def delete_entry(self, path, revision, parent_baton, pool):
292 ### need more logic to detect 'replace'
293 if not fs.is_dir(self.base_root, '/' + path):
294 self._do_diff(path, None)
296 def add_file(self, path, parent_baton,
297 copyfrom_path, copyfrom_revision, file_pool):
298 self._do_diff(None, path)
299 return [ '_', ' ', None ]
301 def open_file(self, path, parent_baton, base_revision, file_pool):
302 return [ '_', ' ', path ]
304 def apply_textdelta(self, file_baton, base_checksum):
305 if file_baton[2] is not None:
306 self._do_diff(file_baton[2], file_baton[2], file_baton[3])
307 return None
309 def _basename(path):
310 "Return the basename for a '/'-separated path."
311 idx = path.rfind('/')
312 if idx == -1:
313 return path
314 return path[idx+1:]
317 def usage(exit):
318 if exit:
319 output = sys.stderr
320 else:
321 output = sys.stdout
323 output.write(
324 "usage: %s REPOS_PATH rev REV [COMMAND] - inspect revision REV\n"
325 " %s REPOS_PATH txn TXN [COMMAND] - inspect transaction TXN\n"
326 " %s REPOS_PATH [COMMAND] - inspect the youngest revision\n"
327 "\n"
328 "REV is a revision number > 0.\n"
329 "TXN is a transaction name.\n"
330 "\n"
331 "If no command is given, the default output (which is the same as\n"
332 "running the subcommands `info' then `tree') will be printed.\n"
333 "\n"
334 "COMMAND can be one of: \n"
335 "\n"
336 " author: print author.\n"
337 " changed: print full change summary: all dirs & files changed.\n"
338 " date: print the timestamp (revisions only).\n"
339 " diff: print GNU-style diffs of changed files and props.\n"
340 " dirs-changed: print changed directories.\n"
341 " ids: print the tree, with nodes ids.\n"
342 " info: print the author, data, log_size, and log message.\n"
343 " log: print log message.\n"
344 " tree: print the tree.\n"
345 "\n"
346 % (sys.argv[0], sys.argv[0], sys.argv[0]))
348 sys.exit(exit)
350 def main():
351 if len(sys.argv) < 2:
352 usage(1)
354 rev = txn = None
356 args = sys.argv[2:]
357 if args:
358 cmd = args[0]
359 if cmd == 'rev':
360 if len(args) == 1:
361 usage(1)
362 try:
363 rev = int(args[1])
364 except ValueError:
365 usage(1)
366 del args[:2]
367 elif cmd == 'txn':
368 if len(args) == 1:
369 usage(1)
370 txn = args[1]
371 del args[:2]
373 if args:
374 if len(args) > 1:
375 usage(1)
376 cmd = args[0].replace('-', '_')
377 else:
378 cmd = 'default'
380 if not hasattr(SVNLook, 'cmd_' + cmd):
381 usage(1)
383 SVNLook(sys.argv[1], cmd, rev, txn)
385 if __name__ == '__main__':
386 main()