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 ######################################################################
22 from svn
import core
, fs
, delta
, repos
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
)
31 self
.txn_ptr
= fs
.open_txn(self
.fs_ptr
, txn
)
35 rev
= fs
.youngest_rev(self
.fs_ptr
)
38 getattr(self
, 'cmd_' + cmd
)()
40 def cmd_default(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 ''
49 def cmd_changed(self
):
50 self
._print
_tree
(ChangedEditor
, pass_root
=1)
56 date
= self
._get
_property
(core
.SVN_PROP_REVISION_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
))
70 self
._print
_tree
(DiffEditor
, pass_root
=1)
72 def cmd_dirs_changed(self
):
73 self
._print
_tree
(DirsChangedEditor
)
76 self
._print
_tree
(Editor
, base_rev
=0, pass_root
=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 ''
91 self
._print
_tree
(Editor
, base_rev
=0)
93 def _get_property(self
, name
):
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):
100 # a specific base rev was not provided. use the transaction base,
101 # or the previous revision
103 base_rev
= fs
.txn_base_revision(self
.txn_ptr
)
105 base_rev
= self
.rev
- 1
107 # get the current root
109 root
= fs
.txn_root(self
.txn_ptr
)
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
)
117 editor
= e_factory(root
, base_root
)
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
):
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):
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.
162 def _get_id(self
, path
):
164 id = fs
.node_id(self
.root
, path
)
165 return ' <%s>' % fs
.unparse_id(id)
169 class DirsChangedEditor(delta
.Editor
):
170 def open_root(self
, base_revision
, dir_pool
):
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
)
181 def open_directory(self
, path
, parent_baton
, base_revision
, dir_pool
):
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
):
197 # the directory hasn't been printed yet. do it.
202 class ChangedEditor(delta
.Editor
):
203 def __init__(self
, root
, base_root
):
205 self
.base_root
= base_root
207 def open_root(self
, base_revision
, dir_pool
):
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
+ '/'
217 def add_directory(self
, path
, parent_baton
,
218 copyfrom_path
, copyfrom_revision
, dir_pool
):
219 print 'A ' + path
+ '/'
222 def open_directory(self
, path
, parent_baton
, base_revision
, dir_pool
):
225 def change_dir_prop(self
, dir_baton
, name
, value
, pool
):
227 # the directory hasn't been printed yet. do it.
228 print '_U ' + dir_baton
[1] + '/'
231 def add_file(self
, path
, parent_baton
,
232 copyfrom_path
, copyfrom_revision
, file_pool
):
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
):
245 def change_file_prop(self
, file_baton
, name
, value
, pool
):
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.
252 status
= text_mod
+ prop_mod
253 # was there some kind of change?
255 print status
+ ' ' + path
258 class DiffEditor(delta
.Editor
):
259 def __init__(self
, root
, base_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
268 print "Removed: " + path
271 print "Modified: " + path
273 print "===============================================================" + \
277 args
.append(label
+ "\t(original)")
279 args
.append(label
+ "\t(new)")
281 differ
= fs
.FileDiff(self
.base_root
, base_path
, self
.root
,
282 path
, diffoptions
=args
)
283 pobj
= differ
.get_pipe()
285 line
= pobj
.readline()
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])
310 "Return the basename for a '/'-separated path."
311 idx
= path
.rfind('/')
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"
328 "REV is a revision number > 0.\n"
329 "TXN is a transaction name.\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"
334 "COMMAND can be one of: \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"
346 % (sys
.argv
[0], sys
.argv
[0], sys
.argv
[0]))
351 if len(sys
.argv
) < 2:
376 cmd
= args
[0].replace('-', '_')
380 if not hasattr(SVNLook
, 'cmd_' + cmd
):
383 SVNLook(sys
.argv
[1], cmd
, rev
, txn
)
385 if __name__
== '__main__':