diff options
author | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 14:29:52 +0100 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 14:29:52 +0100 |
commit | f1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch) | |
tree | 4223b2035bf2240d681a53822808b3c7f687b905 /tools/examples | |
download | subversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz |
Tarball conversion
Diffstat (limited to 'tools/examples')
-rwxr-xr-x | tools/examples/SvnCLBrowse | 489 | ||||
-rwxr-xr-x | tools/examples/blame.py | 113 | ||||
-rwxr-xr-x | tools/examples/check-modified.py | 65 | ||||
-rwxr-xr-x | tools/examples/dumpprops.py | 88 | ||||
-rwxr-xr-x | tools/examples/get-location-segments.py | 118 | ||||
-rwxr-xr-x | tools/examples/getfile.py | 72 | ||||
-rw-r--r-- | tools/examples/getlocks_test.c | 271 | ||||
-rwxr-xr-x | tools/examples/geturl.py | 47 | ||||
-rw-r--r-- | tools/examples/headrev.c | 226 | ||||
-rw-r--r-- | tools/examples/info.rb | 78 | ||||
-rw-r--r-- | tools/examples/minimal_client.c | 285 | ||||
-rwxr-xr-x | tools/examples/putfile.py | 90 | ||||
-rwxr-xr-x | tools/examples/revplist.py | 78 | ||||
-rwxr-xr-x | tools/examples/svnlog2html.rb | 139 | ||||
-rwxr-xr-x | tools/examples/svnlook.py | 441 | ||||
-rwxr-xr-x | tools/examples/svnlook.rb | 516 | ||||
-rw-r--r-- | tools/examples/svnput.c | 352 | ||||
-rw-r--r-- | tools/examples/svnserve-sgid.c | 60 | ||||
-rwxr-xr-x | tools/examples/svnshell.py | 367 | ||||
-rwxr-xr-x | tools/examples/svnshell.rb | 456 | ||||
-rw-r--r-- | tools/examples/testwrite.c | 276 |
21 files changed, 4627 insertions, 0 deletions
diff --git a/tools/examples/SvnCLBrowse b/tools/examples/SvnCLBrowse new file mode 100755 index 0000000..43e16ef --- /dev/null +++ b/tools/examples/SvnCLBrowse @@ -0,0 +1,489 @@ +#!/usr/bin/python +# +# SvnCLBrowse -- graphical Subversion changelist browser +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ==================================================================== + +# This script requires Python 2.5 + +import sys +import os +import getopt + +# Try to import the wxWidgets modules. +try: + import wx + import wx.xrc +except ImportError: + sys.stderr.write(""" +ERROR: This program requires the wxWidgets Python bindings, which you + do not appear to have installed. + +""") + raise + +# Try to import the Subversion modules. +try: + import svn.client, svn.wc, svn.core +except ImportError: + sys.stderr.write(""" +ERROR: This program requires the Subversion Python bindings, which you + do not appear to have installed. + +""") + raise + +status_code_map = { + svn.wc.status_none : ' ', + svn.wc.status_normal : ' ', + svn.wc.status_added : 'A', + svn.wc.status_missing : '!', + svn.wc.status_incomplete : '!', + svn.wc.status_deleted : 'D', + svn.wc.status_replaced : 'R', + svn.wc.status_modified : 'M', + svn.wc.status_merged : 'G', + svn.wc.status_conflicted : 'C', + svn.wc.status_obstructed : '~', + svn.wc.status_ignored : 'I', + svn.wc.status_external : 'X', + svn.wc.status_unversioned : '?', + } + +def output_info(path, info, window): + window.AppendText("Path: %s\n" % os.path.normpath(path)) + if info.kind != svn.core.svn_node_dir: + window.AppendText("Name: %s\n" % os.path.basename(path)) + if info.URL: + window.AppendText("URL: %s\n" % info.URL) + if info.repos_root_URL: + window.AppendText("Repository Root: %s\n" % info.repos_root_URL) + if info.repos_UUID: + window.AppendText("Repository UUID: %s\n" % info.repos_UUID) + if info.rev >= 0: + window.AppendText("Revision: %ld\n" % info.rev) + if info.kind == svn.core.svn_node_file: + window.AppendText("Node Kind: file\n") + elif info.kind == svn.core.svn_node_dir: + window.AppendText("Node Kind: directory\n") + elif info.kind == svn.core.svn_node_none: + window.AppendText("Node Kind: none\n") + else: + window.AppendText("Node Kind: unknown\n") + if info.has_wc_info: + if info.schedule == svn.wc.schedule_normal: + window.AppendText("Schedule: normal\n") + elif info.schedule == svn.wc.schedule_add: + window.AppendText("Schedule: add\n") + elif info.schedule == svn.wc.schedule_delete: + window.AppendText("Schedule: delete\n") + elif info.schedule == svn.wc.schedule_replace: + window.AppendText("Schedule: replace\n") + if info.depth == svn.core.svn_depth_unknown: + pass + elif info.depth == svn.core.svn_depth_empty: + window.AppendText("Depth: empty\n") + elif info.depth == svn.core.svn_depth_files: + window.AppendText("Depth: files\n") + elif info.depth == svn.core.svn_depth_immediates: + window.AppendText("Depth: immediates\n") + elif info.depth == svn.core.svn_depth_infinity: + pass + else: + window.AppendText("Depth: INVALID\n") + if info.copyfrom_url: + window.AppendText("Copied From URL: %s\n" % info.copyfrom_url) + if info.copyfrom_rev >= 0: + window.AppendText("Copied From Rev: %ld\n" % info.copyfrom_rev) + if info.last_changed_author: + window.AppendText("Last Changed Author: %s\n" % info.last_changed_author) + if info.last_changed_rev >= 0: + window.AppendText("Last Changed Rev: %ld\n" % info.last_changed_rev) + if info.last_changed_date: + window.AppendText("Last Changed Date: %s\n" % + svn.core.svn_time_to_human_cstring(info.last_changed_date)) + if info.has_wc_info: + if info.text_time: + window.AppendText("Text Last Updated: %s\n" % + svn.core.svn_time_to_human_cstring(info.text_time)) + if info.prop_time: + window.AppendText("Properties Last Updated: %s\n" % + svn.core.svn_time_to_human_cstring(info.prop_time)) + if info.checksum: + window.AppendText("Checksum: %s\n" % info.checksum) + if info.conflict_old: + window.AppendText("Conflict Previous Base File: %s\n" % info.conflict_old) + if info.conflict_wrk: + window.AppendText("Conflict Previous Working File: %s\n" % info.conflict_wrk) + if info.conflict_new: + window.AppendText("Conflict Current Base File: %s\n" % info.conflict_new) + if info.prejfile: + window.AppendText("Conflict Properties File: %s\n" % info.prejfile) + if info.lock: + if info.lock.token: + window.AppendText("Lock Token: %s\n" % info.lock.token) + if info.lock.owner: + window.AppendText("Lock Owner: %s\n" % info.lock.owner) + if info.lock.creation_date: + window.AppendText("Lock Created: %s\n" % + svn.core.svn_time_to_human_cstring(info.lock.creation_date)) + if info.lock.expiration_date: + window.AppendText("Lock Expires: %s\n" % + svn.core.svn_time_to_human_cstring(info.lock.expiration_date)) + if info.lock.comment: + num_lines = len(info.lock.comment.split("\n")) + window.AppendText("Lock Comment (%d line%s): %s\n" + % (num_lines, num_lines > 1 and "s" or "", info.lock.comment)) + if info.changelist: + window.AppendText("Changelist: %s\n" % info.changelist) + window.AppendText("\n") + +class _item: + pass + +class SvnCLBrowse(wx.App): + def __init__(self, wc_dir): + svn.core.svn_config_ensure(None) + self.svn_ctx = svn.client.ctx_t() + self.svn_ctx.config = svn.core.svn_config_get_config(None) + if wc_dir is not None: + self.wc_dir = svn.core.svn_path_canonicalize(wc_dir) + else: + self.wc_dir = wc_dir + wx.App.__init__(self) + + def OnInit(self): + self.SetAppName("SvnCLBrowse") + + self.xrc = wx.xrc.EmptyXmlResource() + wx.FileSystem.AddHandler(wx.MemoryFSHandler()) + wx.MemoryFSHandler.AddFile('XRC/SvnCLBrowse.xrc', _XML_RESOURCE) + self.xrc.Load('memory:XRC/SvnCLBrowse.xrc') + + # XML Resource stuff. + self.resources = _item() + self.resources.CLBFrame = self.xrc.LoadFrame(None, 'CLBFrame') + self.resources.CLBMenuBar = self.xrc.LoadMenuBar('CLBMenuBar') + self.resources.CLBMenuFileQuit = self.xrc.GetXRCID('CLBMenuFileQuit') + self.resources.CLBMenuOpsInfo = self.xrc.GetXRCID('CLBMenuOpsInfo') + self.resources.CLBMenuOpsMembers = self.xrc.GetXRCID('CLBMenuOpsMembers') + self.resources.CLBMenuHelpAbout = self.xrc.GetXRCID('CLBMenuHelpAbout') + self.resources.CLBDirNav = self.resources.CLBFrame.FindWindowById( + self.xrc.GetXRCID('CLBDirNav')) + self.resources.CLBChangelists = self.resources.CLBFrame.FindWindowById( + self.xrc.GetXRCID('CLBChangelists')) + self.resources.CLBVertSplitter = self.resources.CLBFrame.FindWindowById( + self.xrc.GetXRCID('CLBVertSplitter')) + self.resources.CLBHorzSplitter = self.resources.CLBFrame.FindWindowById( + self.xrc.GetXRCID('CLBHorzSplitter')) + self.resources.CLBOutput = self.resources.CLBFrame.FindWindowById( + self.xrc.GetXRCID('CLBOutput')) + self.resources.CLBStatusBar = self.resources.CLBFrame.CreateStatusBar(2) + + # Glue some of our extra stuff onto the main frame. + self.resources.CLBFrame.SetMenuBar(self.resources.CLBMenuBar) + self.resources.CLBStatusBar.SetStatusWidths([-1, 100]) + + # Event handlers. They are the key to the world. + wx.EVT_CLOSE(self.resources.CLBFrame, self._FrameClosure) + wx.EVT_MENU(self, self.resources.CLBMenuFileQuit, self._FileQuitMenu) + wx.EVT_MENU(self, self.resources.CLBMenuOpsInfo, self._OpsInfoMenu) + wx.EVT_MENU(self, self.resources.CLBMenuOpsMembers, self._OpsMembersMenu) + wx.EVT_MENU(self, self.resources.CLBMenuHelpAbout, self._HelpAboutMenu) + wx.EVT_TREE_ITEM_ACTIVATED(self, self.resources.CLBDirNav.GetTreeCtrl().Id, + self._DirNavSelChanged) + + # Reset our working directory + self._SetWorkingDirectory(self.wc_dir) + + # Resize and display our frame. + self.resources.CLBFrame.SetSize(wx.Size(600, 400)) + self.resources.CLBFrame.Center() + self.resources.CLBFrame.Show(True) + self.resources.CLBVertSplitter.SetSashPosition( + self.resources.CLBVertSplitter.GetSize()[0] / 2) + self.resources.CLBHorzSplitter.SetSashPosition( + self.resources.CLBHorzSplitter.GetSize()[1] / 2) + + # Tell wxWidgets that this is our main window + self.SetTopWindow(self.resources.CLBFrame) + + # Return a success flag + return True + + def _SetWorkingDirectory(self, wc_dir): + if wc_dir is None: + return + if not os.path.isdir(wc_dir): + wc_dir = os.path.abspath('/') + self.wc_dir = os.path.abspath(wc_dir) + self.resources.CLBChangelists.Clear() + self.resources.CLBDirNav.SetPath(self.wc_dir) + self.resources.CLBFrame.SetTitle("SvnCLBrowse - %s" % (self.wc_dir)) + changelists = {} + self.resources.CLBFrame.SetStatusText("Checking '%s' for status..." \ + % (self.wc_dir)) + wx.BeginBusyCursor() + + def _status_callback(path, status, clists=changelists): + if status.entry and status.entry.changelist: + clists[status.entry.changelist] = None + + # Do the status crawl, using _status_callback() as our callback function. + revision = svn.core.svn_opt_revision_t() + revision.type = svn.core.svn_opt_revision_head + try: + svn.client.status2(self.wc_dir, revision, _status_callback, + svn.core.svn_depth_infinity, + False, False, False, True, self.svn_ctx) + except svn.core.SubversionException: + self.resources.CLBStatusBar.SetStatusText("UNVERSIONED", 2) + else: + changelist_names = changelists.keys() + changelist_names.sort() + for changelist in changelist_names: + self.resources.CLBChangelists.Append(changelist) + finally: + wx.EndBusyCursor() + self.resources.CLBFrame.SetStatusText("") + + def _Destroy(self): + self.resources.CLBFrame.Destroy() + + def _DirNavSelChanged(self, event): + self._SetWorkingDirectory(self.resources.CLBDirNav.GetPath()) + + def _GetSelectedChangelists(self): + changelists = [] + items = self.resources.CLBChangelists.GetSelections() + for item in items: + changelists.append(str(self.resources.CLBChangelists.GetString(item))) + return changelists + + def _OpsMembersMenu(self, event): + self.resources.CLBOutput.Clear() + changelists = self._GetSelectedChangelists() + if not changelists: + return + + def _info_receiver(path, info, pool): + self.resources.CLBOutput.AppendText(" %s\n" % (path)) + + for changelist in changelists: + self.resources.CLBOutput.AppendText("Changelist: %s\n" % (changelist)) + revision = svn.core.svn_opt_revision_t() + revision.type = svn.core.svn_opt_revision_working + svn.client.info2(self.wc_dir, revision, revision, + _info_receiver, svn.core.svn_depth_infinity, + [changelist], self.svn_ctx) + self.resources.CLBOutput.AppendText("\n") + + def _OpsInfoMenu(self, event): + self.resources.CLBOutput.Clear() + changelists = self._GetSelectedChangelists() + if not changelists: + return + + def _info_receiver(path, info, pool): + output_info(path, info, self.resources.CLBOutput) + + revision = svn.core.svn_opt_revision_t() + revision.type = svn.core.svn_opt_revision_working + svn.client.info2(self.wc_dir, revision, revision, + _info_receiver, svn.core.svn_depth_infinity, + changelists, self.svn_ctx) + + def _FrameClosure(self, event): + self._Destroy() + + def _FileQuitMenu(self, event): + self._Destroy() + + def _HelpAboutMenu(self, event): + wx.MessageBox("SvnCLBrowse" + " -- graphical Subversion changelist browser.\n\n", + "About SvnCLBrowse", + wx.OK | wx.CENTER, + self.resources.CLBFrame) + + def OnExit(self): + pass + + +_XML_RESOURCE = """<?xml version="1.0" ?> +<resource> + <object class="wxMenuBar" name="CLBMenuBar"> + <object class="wxMenu"> + <label>&File</label> + <object class="wxMenuItem" name="CLBMenuFileQuit"> + <label>&Quit</label> + <accel>CTRL+Q</accel> + <help>Quit SvnCLBrowse.</help> + </object> + </object> + <object class="wxMenu"> + <label>&Subversion</label> + <object class="wxMenuItem" name="CLBMenuOpsInfo"> + <label>&Info</label> + <help>Show information about members of the selected changelist(s).</help> + </object> + <object class="wxMenuItem" name="CLBMenuOpsMembers"> + <label>&Members</label> + <help>List the members of the selected changelist(s).</help> + </object> + </object> + <object class="wxMenu"> + <label>&Help</label> + <object class="wxMenuItem" name="CLBMenuHelpAbout"> + <label>&About...</label> + <help>About SvnCLBrowse.</help> + </object> + </object> + </object> + <object class="wxFrame" name="CLBFrame"> + <title>SvnCLBrowse -- graphical Subversion changelist browser</title> + <centered>1</centered> + <style>wxDEFAULT_FRAME_STYLE|wxCAPTION|wxSYSTEM_MENU|wxRESIZE_BORDER|wxRESIZE_BOX|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxTAB_TRAVERSAL</style> + <object class="wxFlexGridSizer"> + <cols>1</cols> + <rows>1</rows> + <object class="sizeritem"> + <object class="wxSplitterWindow" name="CLBVertSplitter"> + <object class="wxPanel"> + <object class="wxFlexGridSizer"> + <cols>1</cols> + <rows>3</rows> + <growablecols>0</growablecols> + <growablerows>0</growablerows> + <growablerows>1</growablerows> + <growablerows>2</growablerows> + <object class="sizeritem"> + <object class="wxSplitterWindow" name="CLBHorzSplitter"> + <orientation>horizontal</orientation> + <sashpos>200</sashpos> + <minsize>50</minsize> + <style>wxSP_NOBORDER|wxSP_LIVE_UPDATE</style> + <object class="wxPanel"> + <object class="wxStaticBoxSizer"> + <label>Local Modifications</label> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <object class="wxGenericDirCtrl" name="CLBDirNav"> + <style>wxDIRCTRL_DIR_ONLY</style> + </object> + <flag>wxEXPAND</flag> + <option>1</option> + </object> + </object> + </object> + <object class="wxPanel"> + <object class="wxStaticBoxSizer"> + <label>Changelists</label> + <orient>wxHORIZONTAL</orient> + <object class="sizeritem"> + <object class="wxListBox" name="CLBChangelists"> + <content> + <item/></content> + <style>wxLB_MULTIPLE</style> + </object> + <option>1</option> + <flag>wxALL|wxEXPAND</flag> + </object> + </object> + </object> + </object> + <flag>wxEXPAND</flag> + <option>1</option> + </object> + </object> + </object> + <object class="wxPanel"> + <object class="wxFlexGridSizer"> + <cols>1</cols> + <object class="sizeritem"> + <object class="wxStaticBoxSizer"> + <label>Output</label> + <orient>wxVERTICAL</orient> + <object class="sizeritem"> + <object class="wxTextCtrl" name="CLBOutput"> + <style>wxTE_MULTILINE|wxTE_READONLY|wxTE_LEFT|wxTE_DONTWRAP</style> + </object> + <option>1</option> + <flag>wxEXPAND</flag> + </object> + </object> + <option>1</option> + <flag>wxALL|wxEXPAND</flag> + <border>5</border> + </object> + <rows>1</rows> + <growablecols>0</growablecols> + <growablerows>0</growablerows> + </object> + </object> + <orientation>vertical</orientation> + <sashpos>130</sashpos> + <minsize>50</minsize> + <style>wxSP_NOBORDER|wxSP_LIVE_UPDATE</style> + </object> + <option>1</option> + <flag>wxEXPAND</flag> + </object> + <growablecols>0</growablecols> + <growablerows>0</growablerows> + </object> + </object> +</resource> +""" + +def usage_and_exit(errmsg=None): + stream = errmsg and sys.stderr or sys.stdout + progname = os.path.basename(sys.argv[0]) + stream.write("""%s -- graphical Subversion changelist browser + +Usage: %s [DIRECTORY] + +Launch the SvnCLBrowse graphical changelist browser, using DIRECTORY +(or the current working directory, if DIRECTORY is not provided) as +the initial browse location. + +""" % (progname, progname)) + if errmsg: + stream.write("ERROR: %s\n" % (errmsg)) + sys.exit(errmsg and 1 or 0) + +def main(): + opts, args = getopt.gnu_getopt(sys.argv[1:], 'h?', ['help']) + for name, value in opts: + if name == '-h' or name == '-?' or name == '--help': + usage_and_exit() + argc = len(args) + if argc == 0: + wc_dir = '.' + elif argc == 1: + wc_dir = sys.argv[1] + else: + usage_and_exit("Too many arguments") + app = SvnCLBrowse(wc_dir) + app.MainLoop() + app.OnExit() + +if __name__ == "__main__": + main() diff --git a/tools/examples/blame.py b/tools/examples/blame.py new file mode 100755 index 0000000..87d33b3 --- /dev/null +++ b/tools/examples/blame.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# +# USAGE: blame.py [-r REV] repos-path file +# + +import sys +import os +import getopt +try: + my_getopt = getopt.gnu_getopt +except AttributeError: + my_getopt = getopt.getopt +import difflib +from svn import fs, core, repos + +CHUNK_SIZE = 100000 + +def blame(path, filename, rev=None): + + annotresult = {} + path = core.svn_path_canonicalize(path) + + repos_ptr = repos.open(path) + fsob = repos.fs(repos_ptr) + + if rev is None: + rev = fs.youngest_rev(fsob) + filedata = '' + for i in range(0, rev+1): + root = fs.revision_root(fsob, i) + if fs.check_path(root, filename) != core.svn_node_none: + first = i + break + print("First revision is %d" % first) + print("Last revision is %d" % rev) + for i in range(first, rev+1): + previousroot = root + root = fs.revision_root(fsob, i) + if i != first: + if not fs.contents_changed(root, filename, previousroot, filename): + continue + + file = fs.file_contents(root, filename) + previousdata = filedata + filedata = '' + while True: + data = core.svn_stream_read(file, CHUNK_SIZE) + if not data: + break + filedata = filedata + data + + print("Current revision is %d" % i) + diffresult = difflib.ndiff(previousdata.splitlines(1), + filedata.splitlines(1)) + # print ''.join(diffresult) + k = 0 + for j in diffresult: + if j[0] == ' ': + if k in annotresult: + k = k + 1 + continue + else: + annotresult[k] = (i, j[2:]) + k = k + 1 + continue + elif j[0] == '?': + continue + annotresult[k] = (i, j[2:]) + if j[0] != '-': + k = k + 1 +# print ''.join(diffresult) +# print annotresult + for x in range(len(annotresult.keys())): + sys.stdout.write("Line %d (rev %d):%s" % (x, + annotresult[x][0], + annotresult[x][1])) + +def usage(): + print("USAGE: blame.py [-r REV] repos-path file") + sys.exit(1) + +def main(): + opts, args = getopt.getopt(sys.argv[1:], 'r:') + if len(args) != 2: + usage() + rev = None + for name, value in opts: + if name == '-r': + rev = int(value) + blame(args[0], args[1], rev) + +if __name__ == '__main__': + main() diff --git a/tools/examples/check-modified.py b/tools/examples/check-modified.py new file mode 100755 index 0000000..dff3fa1 --- /dev/null +++ b/tools/examples/check-modified.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# +# USAGE: check-modified.py FILE_OR_DIR1 FILE_OR_DIR2 ... +# +# prints out the URL associated with each item +# + +import sys +import os +import os.path +import svn.core +import svn.client +import svn.wc + +FORCE_COMPARISON = 0 + +def usage(): + print("Usage: " + sys.argv[0] + " FILE_OR_DIR1 FILE_OR_DIR2\n") + sys.exit(0) + +def run(files): + + for f in files: + dirpath = fullpath = os.path.abspath(f) + if not os.path.isdir(dirpath): + dirpath = os.path.dirname(dirpath) + + adm_baton = svn.wc.adm_open(None, dirpath, False, True) + + try: + entry = svn.wc.entry(fullpath, adm_baton, 0) + + if svn.wc.text_modified_p(fullpath, FORCE_COMPARISON, + adm_baton): + print("M %s" % f) + else: + print(" %s" % f) + except: + print("? %s" % f) + + svn.wc.adm_close(adm_baton) + +if __name__ == '__main__': + run(sys.argv[1:]) + diff --git a/tools/examples/dumpprops.py b/tools/examples/dumpprops.py new file mode 100755 index 0000000..09c5e6b --- /dev/null +++ b/tools/examples/dumpprops.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# +# USAGE: dumprops.py [-r REV] repos-path [file] +# +# dump out the properties on a given path (recursively if given a dir) +# + +import sys +import os +import getopt +try: + my_getopt = getopt.gnu_getopt +except AttributeError: + my_getopt = getopt.getopt +import pprint + +from svn import fs, core, repos + + +def dumpprops(path, filename='', rev=None): + path = core.svn_path_canonicalize(path) + repos_ptr = repos.open(path) + fsob = repos.fs(repos_ptr) + + if rev is None: + rev = fs.youngest_rev(fsob) + + root = fs.revision_root(fsob, rev) + print_props(root, filename) + if fs.is_dir(root, filename): + walk_tree(root, filename) + +def print_props(root, path): + raw_props = fs.node_proplist(root, path) + # need to massage some buffers into strings for printing + props = { } + for key, value in raw_props.items(): + props[key] = str(value) + + print('--- %s' % path) + pprint.pprint(props) + +def walk_tree(root, path): + for name in fs.dir_entries(root, path).keys(): + full = path + '/' + name + print_props(root, full) + if fs.is_dir(root, full): + walk_tree(root, full) + +def usage(): + print("USAGE: dumpprops.py [-r REV] repos-path [file]") + sys.exit(1) + +def main(): + opts, args = my_getopt(sys.argv[1:], 'r:') + rev = None + for name, value in opts: + if name == '-r': + rev = int(value) + if len(args) == 2: + dumpprops(args[0], args[1], rev) + elif len(args) == 1: + dumpprops(args[0], "", rev) + else: + usage() + +if __name__ == '__main__': + main() diff --git a/tools/examples/get-location-segments.py b/tools/examples/get-location-segments.py new file mode 100755 index 0000000..c084dae --- /dev/null +++ b/tools/examples/get-location-segments.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +import sys +import os +from svn import client, ra, core + +def printer(segment, pool): + path = segment.path is not None and segment.path or "(null)" + print("r%d-r%d: %s" % (segment.range_start, segment.range_end, path)) + + +def parse_args(args): + argc = len(sys.argv) + + # parse the target URL and optional peg revision + path_pieces = args[0].split('@') + if len(path_pieces) > 1: + peg_revision = int(path_pieces[-1]) + assert peg_revision >= 0 + url = '@'.join(path_pieces[:-1]) + else: + peg_revision = core.SVN_INVALID_REVNUM + url = path_pieces[0] + url = core.svn_path_canonicalize(url) + + # parse the revision range, if any + if argc > 2: + rev_pieces = args[1].split(':') + num_revs = len(rev_pieces) + assert num_revs < 3 + if num_revs == 2: + start_revision = int(rev_pieces[0]) + end_revision = int(rev_pieces[1]) + else: + start_revision = end_revision = int(rev_pieces[0]) + assert(start_revision >= 0) + assert(end_revision >= 0) + else: + start_revision = peg_revision + end_revision = 0 + + # validate + if start_revision >= 0 \ + and end_revision >= 0 \ + and end_revision > start_revision: + raise Exception("End revision must not be younger than start revision") + if peg_revision >= 0 \ + and start_revision >= 0 \ + and start_revision > peg_revision: + raise Exception("Start revision must not be younger than peg revision") + + return url, peg_revision, start_revision, end_revision + + +def main(): + try: + url, peg_revision, start_revision, end_revision = parse_args(sys.argv[1:]) + except Exception, e: + sys.stderr.write("""Usage: %s URL[@PEG-REV] [START-REV[:END-REV]] + +Trace the history of URL@PEG-REV, printing the location(s) of its +existence between START-REV and END-REV. If START-REV is not +provided, the entire history of URL@PEG-REV back to its origin will be +displayed. If provided, START-REV must not be younger than PEG-REV. +If END-REV is provided, it must not be younger than START-REV. + +(This is a wrapper around Subversion's svn_ra_get_location_segments() API.) + +ERROR: %s +""" % (os.path.basename(sys.argv[0]), str(e))) + sys.exit(1) + + core.svn_config_ensure(None) + ctx = client.ctx_t() + + # Make sure that these are at the start of the list, so passwords from + # gnome-keyring / kwallet are checked before asking for new passwords. + # Note that we don't pass our config here, since we can't seem to access + # ctx.config.config (ctx.config is opaque). + providers = core.svn_auth_get_platform_specific_client_providers(None, None) + providers.extend([ + client.get_simple_provider(), + client.get_username_provider(), + client.get_ssl_server_trust_file_provider(), + client.get_ssl_client_cert_file_provider(), + client.get_ssl_client_cert_pw_file_provider(), + ]) + + ctx.auth_baton = core.svn_auth_open(providers) + ctx.config = core.svn_config_get_config(None) + + ra_callbacks = ra.callbacks_t() + ra_callbacks.auth_baton = ctx.auth_baton + ra_session = ra.open(url, ra_callbacks, None, ctx.config) + ra.get_location_segments(ra_session, "", peg_revision, + start_revision, end_revision, printer) + +if __name__ == "__main__": + main() diff --git a/tools/examples/getfile.py b/tools/examples/getfile.py new file mode 100755 index 0000000..b8db211 --- /dev/null +++ b/tools/examples/getfile.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# +# USAGE: getfile.py [-r REV] repos-path file +# +# gets a file from an SVN repository, puts it to sys.stdout +# + +import sys +import os +import getopt +try: + my_getopt = getopt.gnu_getopt +except AttributeError: + my_getopt = getopt.getopt + +from svn import fs, core, repos + +CHUNK_SIZE = 16384 + +def getfile(path, filename, rev=None): + path = core.svn_path_canonicalize(path) + repos_ptr = repos.open(path) + fsob = repos.fs(repos_ptr) + + if rev is None: + rev = fs.youngest_rev(fsob) + print("Using youngest revision %s" % rev) + + root = fs.revision_root(fsob, rev) + file = fs.file_contents(root, filename) + while True: + data = core.svn_stream_read(file, CHUNK_SIZE) + if not data: + break + sys.stdout.write(data) + +def usage(): + print("USAGE: getfile.py [-r REV] repos-path file") + sys.exit(1) + +def main(): + opts, args = my_getopt(sys.argv[1:], 'r:') + if len(args) != 2: + usage() + rev = None + for name, value in opts: + if name == '-r': + rev = int(value) + getfile(args[0], args[1], rev) + +if __name__ == '__main__': + main() diff --git a/tools/examples/getlocks_test.c b/tools/examples/getlocks_test.c new file mode 100644 index 0000000..1051063 --- /dev/null +++ b/tools/examples/getlocks_test.c @@ -0,0 +1,271 @@ +/* + * getlocks_test.c : show all repository locks living below a URL + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * To compile on unix against Subversion and APR libraries, try + * something like: + * + * cc getlocks_test.c -o getlocks_test \ + * -I/usr/local/include/subversion-1 -I/usr/local/apache2/include \ + * -L/usr/local/apache2/lib -L/usr/local/lib \ + * -lsvn_client-1 -lsvn_ra-1 -lsvn_subr-1 -lapr-0 -laprutil-0 + * + */ + +#include "svn_client.h" +#include "svn_pools.h" +#include "svn_config.h" +#include "svn_cmdline.h" +#include "svn_time.h" +#include "svn_fs.h" +#include "svn_path.h" + +/* Display a prompt and read a one-line response into the provided buffer, + removing a trailing newline if present. */ +static svn_error_t * +prompt_and_read_line(const char *prompt, + char *buffer, + size_t max) +{ + int len; + printf("%s: ", prompt); + if (fgets(buffer, max, stdin) == NULL) + return svn_error_create(0, NULL, "error reading stdin"); + len = strlen(buffer); + if (len > 0 && buffer[len-1] == '\n') + buffer[len-1] = 0; + return SVN_NO_ERROR; +} + +/* A tiny callback function of type 'svn_auth_simple_prompt_func_t'. For + a much better example, see svn_cl__auth_simple_prompt in the official + svn cmdline client. */ +static svn_error_t * +my_simple_prompt_callback (svn_auth_cred_simple_t **cred, + void *baton, + const char *realm, + const char *username, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_simple_t *ret = apr_pcalloc (pool, sizeof (*ret)); + char answerbuf[100]; + + if (realm) + { + printf ("Authentication realm: %s\n", realm); + } + + if (username) + ret->username = apr_pstrdup (pool, username); + else + { + SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf))); + ret->username = apr_pstrdup (pool, answerbuf); + } + + SVN_ERR (prompt_and_read_line("Password", answerbuf, sizeof(answerbuf))); + ret->password = apr_pstrdup (pool, answerbuf); + + *cred = ret; + return SVN_NO_ERROR; +} + + +/* A tiny callback function of type 'svn_auth_username_prompt_func_t'. For + a much better example, see svn_cl__auth_username_prompt in the official + svn cmdline client. */ +static svn_error_t * +my_username_prompt_callback (svn_auth_cred_username_t **cred, + void *baton, + const char *realm, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_username_t *ret = apr_pcalloc (pool, sizeof (*ret)); + char answerbuf[100]; + + if (realm) + { + printf ("Authentication realm: %s\n", realm); + } + + SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf))); + ret->username = apr_pstrdup (pool, answerbuf); + + *cred = ret; + return SVN_NO_ERROR; +} + + +/* A callback function used when the RA layer needs a handle to a + temporary file. This is a reduced version of the callback used in + the official svn cmdline client. */ +static svn_error_t * +open_tmp_file (apr_file_t **fp, + void *callback_baton, + apr_pool_t *pool) +{ + const char *path; + const char *ignored_filename; + + SVN_ERR (svn_io_temp_dir (&path, pool)); + path = svn_path_join (path, "tempfile", pool); + + /* Open a unique file, with delete-on-close set. */ + SVN_ERR (svn_io_open_unique_file2 (fp, &ignored_filename, + path, ".tmp", + svn_io_file_del_on_close, pool)); + + return SVN_NO_ERROR; +} + + + +int +main (int argc, const char **argv) +{ + apr_pool_t *pool; + svn_error_t *err; + apr_hash_t *locks; + apr_hash_index_t *hi; + const char *URL; + svn_ra_session_t *session; + svn_ra_callbacks_t *cbtable; + apr_hash_t *cfg_hash; + svn_auth_baton_t *auth_baton; + + if (argc <= 1) + { + printf ("Usage: %s URL\n", argv[0]); + printf (" Print all locks at or below URL.\n"); + return EXIT_FAILURE; + } + URL = argv[1]; + + /* Initialize the app. Send all error messages to 'stderr'. */ + if (svn_cmdline_init ("ra_test", stderr) != EXIT_SUCCESS) + return EXIT_FAILURE; + + /* Create top-level memory pool. Be sure to read the HACKING file to + understand how to properly use/free subpools. */ + pool = svn_pool_create (NULL); + + /* Initialize the FS library. */ + err = svn_fs_initialize (pool); + if (err) goto hit_error; + + /* Make sure the ~/.subversion run-time config files exist, and load. */ + err = svn_config_ensure (NULL, pool); + if (err) goto hit_error; + + err = svn_config_get_config (&cfg_hash, NULL, pool); + if (err) goto hit_error; + + /* Build an authentication baton. */ + { + /* There are many different kinds of authentication back-end + "providers". See svn_auth.h for a full overview. */ + svn_auth_provider_object_t *provider; + apr_array_header_t *providers + = apr_array_make (pool, 4, sizeof (svn_auth_provider_object_t *)); + + svn_client_get_simple_prompt_provider (&provider, + my_simple_prompt_callback, + NULL, /* baton */ + 2, /* retry limit */ pool); + APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; + + svn_client_get_username_prompt_provider (&provider, + my_username_prompt_callback, + NULL, /* baton */ + 2, /* retry limit */ pool); + APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; + + /* Register the auth-providers into the context's auth_baton. */ + svn_auth_open (&auth_baton, providers, pool); + } + + /* Create a table of callbacks for the RA session, mostly nonexistent. */ + cbtable = apr_pcalloc (pool, sizeof(*cbtable)); + cbtable->auth_baton = auth_baton; + cbtable->open_tmp_file = open_tmp_file; + + /* Now do the real work. */ + + err = svn_ra_open (&session, URL, cbtable, NULL, cfg_hash, pool); + if (err) goto hit_error; + + err = svn_ra_get_locks (session, &locks, "", pool); + if (err) goto hit_error; + + err = svn_cmdline_printf (pool, "\n"); + if (err) goto hit_error; + + for (hi = apr_hash_first (pool, locks); hi; hi = apr_hash_next (hi)) + { + const void *key; + void *val; + const char *path, *cr_date, *exp_date; + svn_lock_t *lock; + + apr_hash_this (hi, &key, NULL, &val); + path = key; + lock = val; + + cr_date = svn_time_to_human_cstring (lock->creation_date, pool); + + if (lock->expiration_date) + exp_date = svn_time_to_human_cstring (lock->expiration_date, pool); + else + exp_date = "never"; + + err = svn_cmdline_printf (pool, "%s\n", path); + if (err) goto hit_error; + + err = svn_cmdline_printf (pool, + " UUID Token: %s\n", lock->token); + if (err) goto hit_error; + + err = svn_cmdline_printf (pool, + " Owner: %s\n", lock->owner); + if (err) goto hit_error; + + err = svn_cmdline_printf (pool, + " Comment: %s\n", + lock->comment ? lock->comment : "none"); + if (err) goto hit_error; + + err = svn_cmdline_printf (pool, + " Created: %s\n", cr_date); + if (err) goto hit_error; + + err = svn_cmdline_printf (pool, + " Expires: %s\n\n", exp_date); + if (err) goto hit_error; + } + + return EXIT_SUCCESS; + + hit_error: + svn_handle_error2 (err, stderr, FALSE, "getlocks_test: "); + return EXIT_FAILURE; +} diff --git a/tools/examples/geturl.py b/tools/examples/geturl.py new file mode 100755 index 0000000..d50e22e --- /dev/null +++ b/tools/examples/geturl.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# +# USAGE: geturl.py FILE_OR_DIR1 FILE_OR_DIR2 ... +# +# prints out the URL associated with each item +# + +import os +import sys + +import svn.wc +import svn.core + +def main(files): + for f in files: + dirpath = fullpath = os.path.abspath(f) + if not os.path.isdir(dirpath): + dirpath = os.path.dirname(dirpath) + adm_baton = svn.wc.adm_open(None, dirpath, 1, 1) + try: + entry = svn.wc.entry(fullpath, adm_baton, 0) + print(entry.url) + finally: + svn.wc.adm_close(adm_baton) + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/tools/examples/headrev.c b/tools/examples/headrev.c new file mode 100644 index 0000000..1f71d76 --- /dev/null +++ b/tools/examples/headrev.c @@ -0,0 +1,226 @@ +/* + * headrev.c : print out the HEAD revision of a repository. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * To compile on unix against Subversion and APR libraries, try + * something like: + * + * cc headrev.c -o headrev \ + * -I/usr/local/include/subversion-1 -I/usr/local/apache2/include \ + * -L/usr/local/apache2/lib -L/usr/local/lib \ + * -lsvn_client-1 -lsvn_ra-1 -lsvn_subr-1 -lapr-0 -laprutil-0 + * + */ + +#include "svn_client.h" +#include "svn_pools.h" +#include "svn_config.h" +#include "svn_fs.h" +#include "svn_path.h" +#include "svn_cmdline.h" + + +/* Display a prompt and read a one-line response into the provided buffer, + removing a trailing newline if present. */ +static svn_error_t * +prompt_and_read_line(const char *prompt, + char *buffer, + size_t max) +{ + int len; + printf("%s: ", prompt); + if (fgets(buffer, max, stdin) == NULL) + return svn_error_create(0, NULL, "error reading stdin"); + len = strlen(buffer); + if (len > 0 && buffer[len-1] == '\n') + buffer[len-1] = 0; + return SVN_NO_ERROR; +} + +/* A tiny callback function of type 'svn_auth_simple_prompt_func_t'. For + a much better example, see svn_cl__auth_simple_prompt in the official + svn cmdline client. */ +static svn_error_t * +my_simple_prompt_callback (svn_auth_cred_simple_t **cred, + void *baton, + const char *realm, + const char *username, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_simple_t *ret = apr_pcalloc (pool, sizeof (*ret)); + char answerbuf[100]; + + if (realm) + { + printf ("Authentication realm: %s\n", realm); + } + + if (username) + ret->username = apr_pstrdup (pool, username); + else + { + SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf))); + ret->username = apr_pstrdup (pool, answerbuf); + } + + SVN_ERR (prompt_and_read_line("Password", answerbuf, sizeof(answerbuf))); + ret->password = apr_pstrdup (pool, answerbuf); + + *cred = ret; + return SVN_NO_ERROR; +} + + +/* A tiny callback function of type 'svn_auth_username_prompt_func_t'. For + a much better example, see svn_cl__auth_username_prompt in the official + svn cmdline client. */ +static svn_error_t * +my_username_prompt_callback (svn_auth_cred_username_t **cred, + void *baton, + const char *realm, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_username_t *ret = apr_pcalloc (pool, sizeof (*ret)); + char answerbuf[100]; + + if (realm) + { + printf ("Authentication realm: %s\n", realm); + } + + SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf))); + ret->username = apr_pstrdup (pool, answerbuf); + + *cred = ret; + return SVN_NO_ERROR; +} + + +/* A callback function used when the RA layer needs a handle to a + temporary file. This is a reduced version of the callback used in + the official svn cmdline client. */ +static svn_error_t * +open_tmp_file (apr_file_t **fp, + void *callback_baton, + apr_pool_t *pool) +{ + const char *path; + const char *ignored_filename; + + SVN_ERR (svn_io_temp_dir (&path, pool)); + path = svn_path_join (path, "tempfile", pool); + + /* Open a unique file, with delete-on-close set. */ + SVN_ERR (svn_io_open_unique_file2 (fp, &ignored_filename, + path, ".tmp", + svn_io_file_del_on_close, pool)); + + return SVN_NO_ERROR; +} + + +int +main (int argc, const char **argv) +{ + apr_pool_t *pool; + svn_error_t *err; + const char *URL; + svn_ra_session_t *session; + svn_ra_callbacks2_t *cbtable; + svn_revnum_t rev; + apr_hash_t *cfg_hash; + svn_auth_baton_t *auth_baton; + + if (argc <= 1) + { + printf ("Usage: %s URL\n", argv[0]); + printf (" Print HEAD revision of URL's repository.\n"); + return EXIT_FAILURE; + } + else + URL = argv[1]; + + /* Initialize the app. Send all error messages to 'stderr'. */ + if (svn_cmdline_init ("headrev", stderr) != EXIT_SUCCESS) + return EXIT_FAILURE; + + /* Create top-level memory pool. Be sure to read the HACKING file to + understand how to properly use/free subpools. */ + pool = svn_pool_create (NULL); + + /* Initialize the FS library. */ + err = svn_fs_initialize (pool); + if (err) goto hit_error; + + /* Make sure the ~/.subversion run-time config files exist, and load. */ + err = svn_config_ensure (NULL, pool); + if (err) goto hit_error; + + err = svn_config_get_config (&cfg_hash, NULL, pool); + if (err) goto hit_error; + + /* Build an authentication baton. */ + { + /* There are many different kinds of authentication back-end + "providers". See svn_auth.h for a full overview. */ + svn_auth_provider_object_t *provider; + apr_array_header_t *providers + = apr_array_make (pool, 4, sizeof (svn_auth_provider_object_t *)); + + svn_client_get_simple_prompt_provider (&provider, + my_simple_prompt_callback, + NULL, /* baton */ + 2, /* retry limit */ pool); + APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; + + svn_client_get_username_prompt_provider (&provider, + my_username_prompt_callback, + NULL, /* baton */ + 2, /* retry limit */ pool); + APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; + + /* Register the auth-providers into the context's auth_baton. */ + svn_auth_open (&auth_baton, providers, pool); + } + + /* Create a table of callbacks for the RA session, mostly nonexistent. */ + cbtable = apr_pcalloc (pool, sizeof(*cbtable)); + cbtable->auth_baton = auth_baton; + cbtable->open_tmp_file = open_tmp_file; + + /* Now do the real work. */ + + err = svn_ra_open2(&session, URL, cbtable, NULL, cfg_hash, pool); + if (err) goto hit_error; + + err = svn_ra_get_latest_revnum(session, &rev, pool); + if (err) goto hit_error; + + printf ("The latest revision is %ld.\n", rev); + + return EXIT_SUCCESS; + + hit_error: + svn_handle_error2 (err, stderr, FALSE, "headrev: "); + return EXIT_FAILURE; +} diff --git a/tools/examples/info.rb b/tools/examples/info.rb new file mode 100644 index 0000000..fad6837 --- /dev/null +++ b/tools/examples/info.rb @@ -0,0 +1,78 @@ +#!/usr/bin/env ruby +# +# info.rb : output some info about a subversion url +# +# Example based on a blogpost by Mark Deepwell +# http://www.markdeepwell.com/2010/06/ruby-subversion-bindings/ +# +###################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### +# + +require "svn/core" +require "svn/client" +require "svn/wc" +require "svn/repos" + +# Prompt function mimicking svn's own prompt +simple_prompt = Proc.new do + |result, realm, username, default, may_save, pool| + + puts "Authentication realm: #{realm}" + if username != nil + result.username = username + else + print "Username: " + result.username = STDIN.gets.strip + end + print "Password for '#{result.username}': " + result.password = STDIN.gets.strip +end + + +if ARGV.length != 1 + puts "Usage: info.rb URL[@REV]" +else + ctx = Svn::Client::Context.new() + ctx.add_platform_specific_client_providers + ctx.add_simple_provider + ctx.add_simple_prompt_provider(2, simple_prompt) + ctx.add_username_provider + ctx.add_ssl_server_trust_file_provider + ctx.add_ssl_client_cert_file_provider + ctx.add_ssl_client_cert_pw_file_provider + + repos_uri, revision = ARGV[0].split("@", 2) + if revision + revision = Integer(revision) + end + + begin + ctx.info(repos_uri, revision) do |path, info| + puts("Url: #{info.url}") + puts("Last changed rev: #{info.last_changed_rev}") + puts("Last changed author: #{info.last_changed_author}") + puts("Last changed date: #{info.last_changed_date}") + puts("Kind: #{info.kind}") + end + rescue Svn::Error => e + # catch a generic svn error + raise "Failed to retrieve SVN info at revision " + revision.to_s + end +end diff --git a/tools/examples/minimal_client.c b/tools/examples/minimal_client.c new file mode 100644 index 0000000..967ce5b --- /dev/null +++ b/tools/examples/minimal_client.c @@ -0,0 +1,285 @@ +/* + * minimal_client.c - a minimal Subversion client application ("hello world") + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This app demonstrates how to use the svn_client.h API. + * + * It reads a directory URL from the commandline, runs + * svn_client_list() and prints the list of directory-entries. It + * also knows how to deal with basic username/password authentication + * challenges. + * + * For a much more complex example, the svn cmdline client might be + * considered the 'reference implementation'. + * + * From a Linux system, a typical commandline compile might look like: + * + * cc minimal_client.c -o minimal_client \ + * -I/usr/local/include/subversion-1 -I/usr/local/apache2/include \ + * -L/usr/local/apache2/lib -L/usr/local/lib \ + * -lsvn_client-1 -lapr-0 -laprutil-0 + * + */ + + +#include "svn_client.h" +#include "svn_cmdline.h" +#include "svn_pools.h" +#include "svn_config.h" +#include "svn_fs.h" + + +/* Display a prompt and read a one-line response into the provided buffer, + removing a trailing newline if present. */ +static svn_error_t * +prompt_and_read_line(const char *prompt, + char *buffer, + size_t max) +{ + int len; + printf("%s: ", prompt); + if (fgets(buffer, max, stdin) == NULL) + return svn_error_create(0, NULL, "error reading stdin"); + len = strlen(buffer); + if (len > 0 && buffer[len-1] == '\n') + buffer[len-1] = 0; + return SVN_NO_ERROR; +} + +/* A tiny callback function of type 'svn_auth_simple_prompt_func_t'. For + a much better example, see svn_cl__auth_simple_prompt in the official + svn cmdline client. */ +static svn_error_t * +my_simple_prompt_callback (svn_auth_cred_simple_t **cred, + void *baton, + const char *realm, + const char *username, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_simple_t *ret = apr_pcalloc (pool, sizeof (*ret)); + char answerbuf[100]; + + if (realm) + { + printf ("Authentication realm: %s\n", realm); + } + + if (username) + ret->username = apr_pstrdup (pool, username); + else + { + SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf))); + ret->username = apr_pstrdup (pool, answerbuf); + } + + SVN_ERR (prompt_and_read_line("Password", answerbuf, sizeof(answerbuf))); + ret->password = apr_pstrdup (pool, answerbuf); + + *cred = ret; + return SVN_NO_ERROR; +} + + +/* A tiny callback function of type 'svn_auth_username_prompt_func_t'. For + a much better example, see svn_cl__auth_username_prompt in the official + svn cmdline client. */ +static svn_error_t * +my_username_prompt_callback (svn_auth_cred_username_t **cred, + void *baton, + const char *realm, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_username_t *ret = apr_pcalloc (pool, sizeof (*ret)); + char answerbuf[100]; + + if (realm) + { + printf ("Authentication realm: %s\n", realm); + } + + SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf))); + ret->username = apr_pstrdup (pool, answerbuf); + + *cred = ret; + return SVN_NO_ERROR; +} + + + +int +main (int argc, const char **argv) +{ + apr_pool_t *pool; + svn_error_t *err; + svn_opt_revision_t revision; + apr_hash_t *dirents; + apr_hash_index_t *hi; + svn_client_ctx_t *ctx; + const char *URL; + + if (argc <= 1) + { + printf ("Usage: %s URL\n", argv[0]); + return EXIT_FAILURE; + } + else + URL = argv[1]; + + /* Initialize the app. Send all error messages to 'stderr'. */ + if (svn_cmdline_init ("minimal_client", stderr) != EXIT_SUCCESS) + return EXIT_FAILURE; + + /* Create top-level memory pool. Be sure to read the HACKING file to + understand how to properly use/free subpools. */ + pool = svn_pool_create (NULL); + + /* Initialize the FS library. */ + err = svn_fs_initialize (pool); + if (err) + { + /* For functions deeper in the stack, we usually use the + SVN_ERR() exception-throwing macro (see svn_error.h). At the + top level, we catch & print the error with svn_handle_error2(). */ + svn_handle_error2 (err, stderr, FALSE, "minimal_client: "); + return EXIT_FAILURE; + } + + /* Make sure the ~/.subversion run-time config files exist */ + err = svn_config_ensure (NULL, pool); + if (err) + { + svn_handle_error2 (err, stderr, FALSE, "minimal_client: "); + return EXIT_FAILURE; + } + + /* All clients need to fill out a client_ctx object. */ + { + /* Initialize and allocate the client_ctx object. */ + if ((err = svn_client_create_context (&ctx, pool))) + { + svn_handle_error2 (err, stderr, FALSE, "minimal_client: "); + return EXIT_FAILURE; + } + + /* Load the run-time config file into a hash */ + if ((err = svn_config_get_config (&(ctx->config), NULL, pool))) + { + svn_handle_error2 (err, stderr, FALSE, "minimal_client: "); + return EXIT_FAILURE; + } + +#ifdef WIN32 + /* Set the working copy administrative directory name. */ + if (getenv ("SVN_ASP_DOT_NET_HACK")) + { + err = svn_wc_set_adm_dir ("_svn", pool); + if (err) + { + svn_handle_error2 (err, stderr, FALSE, "minimal_client: "); + return EXIT_FAILURE; + } + } +#endif + + /* Depending on what your client does, you'll want to read about + (and implement) the various callback function types below. */ + + /* A func (& context) which receives event signals during + checkouts, updates, commits, etc. */ + /* ctx->notify_func = my_notification_func; + ctx->notify_baton = NULL; */ + + /* A func (& context) which can receive log messages */ + /* ctx->log_msg_func = my_log_msg_receiver_func; + ctx->log_msg_baton = NULL; */ + + /* A func (& context) which checks whether the user cancelled */ + /* ctx->cancel_func = my_cancel_checking_func; + ctx->cancel_baton = NULL; */ + + /* Make the client_ctx capable of authenticating users */ + { + /* There are many different kinds of authentication back-end + "providers". See svn_auth.h for a full overview. + + If you want to get the auth behavior of the 'svn' program, + you can use svn_cmdline_setup_auth_baton, which will give + you the exact set of auth providers it uses. This program + doesn't use it because it's only appropriate for a command + line program, and this is supposed to be a general purpose + example. */ + + svn_auth_provider_object_t *provider; + apr_array_header_t *providers + = apr_array_make (pool, 4, sizeof (svn_auth_provider_object_t *)); + + svn_auth_get_simple_prompt_provider (&provider, + my_simple_prompt_callback, + NULL, /* baton */ + 2, /* retry limit */ pool); + APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; + + svn_auth_get_username_prompt_provider (&provider, + my_username_prompt_callback, + NULL, /* baton */ + 2, /* retry limit */ pool); + APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; + + /* Register the auth-providers into the context's auth_baton. */ + svn_auth_open (&ctx->auth_baton, providers, pool); + } + } /* end of client_ctx setup */ + + + /* Now do the real work. */ + + /* Set revision to always be the HEAD revision. It could, however, + be set to a specific revision number, date, or other values. */ + revision.kind = svn_opt_revision_head; + + /* Main call into libsvn_client does all the work. */ + err = svn_client_ls (&dirents, + URL, &revision, + FALSE, /* no recursion */ + ctx, pool); + if (err) + { + svn_handle_error2 (err, stderr, FALSE, "minimal_client: "); + return EXIT_FAILURE; + } + + /* Print the dir entries in the hash. */ + for (hi = apr_hash_first (pool, dirents); hi; hi = apr_hash_next (hi)) + { + const char *entryname; + svn_dirent_t *val; + + apr_hash_this (hi, (void *) &entryname, NULL, (void *) &val); + printf (" %s\n", entryname); + + /* 'val' is actually an svn_dirent_t structure; a more complex + program would mine it for extra printable information. */ + } + + return EXIT_SUCCESS; +} diff --git a/tools/examples/putfile.py b/tools/examples/putfile.py new file mode 100755 index 0000000..4b028aa --- /dev/null +++ b/tools/examples/putfile.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# +# USAGE: putfile.py [-m commitmsg] [-u username] file repos-path +# +# put a file into an SVN repository +# + +import sys +import os +import getopt +try: + my_getopt = getopt.gnu_getopt +except AttributeError: + my_getopt = getopt.getopt + +from svn import fs, core, repos, delta + +def putfile(fname, rpath, uname="", commitmsg=""): + rpath = core.svn_path_canonicalize(rpath) + repos_ptr = repos.open(rpath) + fsob = repos.fs(repos_ptr) + + # open a transaction against HEAD + rev = fs.youngest_rev(fsob) + + txn = repos.fs_begin_txn_for_commit(repos_ptr, rev, uname, commitmsg) + + root = fs.txn_root(txn) + rev_root = fs.revision_root(fsob, rev) + + kind = fs.check_path(root, fname) + if kind == core.svn_node_none: + print("file '%s' does not exist, creating..." % fname) + fs.make_file(root, fname) + elif kind == core.svn_node_dir: + print("File '%s' is a dir." % fname) + return + else: + print("Updating file '%s'" % fname) + + handler, baton = fs.apply_textdelta(root, fname, None, None) + + ### it would be nice to get an svn_stream_t. for now, just load in the + ### whole file and shove it into the FS. + delta.svn_txdelta_send_string(open(fname, 'rb').read(), + handler, baton) + + newrev = repos.fs_commit_txn(repos_ptr, txn) + print("revision: %s" % newrev) + +def usage(): + print("USAGE: putfile.py [-m commitmsg] [-u username] file repos-path") + sys.exit(1) + +def main(): + opts, args = my_getopt(sys.argv[1:], 'm:u:') + if len(args) != 2: + usage() + + uname = commitmsg = "" + + for name, value in opts: + if name == '-u': + uname = value + if name == '-m': + commitmsg = value + putfile(args[0], args[1], uname, commitmsg) + +if __name__ == '__main__': + main() diff --git a/tools/examples/revplist.py b/tools/examples/revplist.py new file mode 100755 index 0000000..5e22928 --- /dev/null +++ b/tools/examples/revplist.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# +# revplist.py : display revision properties +# +###################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### +# + +import sys +import os +import getopt +try: + my_getopt = getopt.gnu_getopt +except AttributeError: + my_getopt = getopt.getopt + +from svn import fs, core + +def plist(rev=None, home='.', *props): + + db_path = os.path.join(home, 'db') + if not os.path.exists(db_path): + db_path = home + + fs_ptr = fs.new(None) + fs.open_berkeley(fs_ptr, db_path) + + if rev is None: + rev = fs.youngest_rev(fs_ptr) + + print('Properties for revision: %s' % rev) + if props: + for propname in props: + value = fs.revision_prop(fs_ptr, rev, propname) + if value is None: + print('%s: <not present>' % propname) + else: + print('%s: %s' % (propname, value)) + else: + proplist = fs.revision_proplist(fs_ptr, rev) + for propname, value in proplist.items(): + print('%s: %s' % (propname, value)) + +def usage(): + print("USAGE: %s [-r REV] [-h DBHOME] [PROP1 [PROP2 ...]]" % sys.argv[0]) + sys.exit(1) + +def main(): + ### how to invoke usage() ? + opts, args = my_getopt(sys.argv[1:], 'r:h:') + rev = None + home = '.' + for name, value in opts: + if name == '-r': + rev = int(value) + elif name == '-h': + home = value + + plist(rev, home, *args) + +if __name__ == '__main__': + main() diff --git a/tools/examples/svnlog2html.rb b/tools/examples/svnlog2html.rb new file mode 100755 index 0000000..a7571cc --- /dev/null +++ b/tools/examples/svnlog2html.rb @@ -0,0 +1,139 @@ +#!/usr/bin/env ruby + +# +###################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### +# + +require "erb" +require "svn/client" + +include ERB::Util + +path = File.expand_path(ARGV.shift || Dir.pwd) + +html = <<-HEADER +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <style type="text/css"> +div.entry +{ + border: 1px solid red; + border-width: 1px 0 0 1px; + margin: 2em 2em 2em 3em; + padding: 0 2em; +} + +pre.message +{ + border-left: 1px solid red; + margin: 1em 2em; + padding-left: 1em; +} + +div.info +{ + text-align: right; +} + +span.info +{ + border-bottom: 1px solid red; + padding: 0 5px 1px 1em; +} + +span.author +{ + font-style: italic; +} + +span.date +{ + color: #999; +} + +li.action-A +{ + color: blue; +} + +li.action-M +{ + color: green; +} + +li.action-D +{ + color: red; + text-decoration: line-through; +} + </style> + <title>#{h path}</title> +</head> +<body> +<h1>#{h path}</h1> +HEADER + +ctx = Svn::Client::Context.new +ctx.log(path, "HEAD", 0, 40, true, true) do + |changed_paths, rev, author, date, message| + + html << <<-ENTRY_HEADER + +<div class="entry"> + <h2>r#{h rev}</h2> + <pre class="message">#{h message}</pre> + <div class="info"> + <span class="info"> + by <span class="author">#{h author}</span> + at <span class="date">#{date}</span> + </span> + </div> + <div class="changed-path"> +ENTRY_HEADER + + changed_paths.sort.each do |path, changed_path| + action = changed_path.action + html << <<-ENTRY_PATH + <ul> + <li class="action-#{h action}"> + <span class="action">#{h action}</span>: + <span class="changed-path">#{h path}</span> + </li> + </ul> +ENTRY_PATH + end + + html << <<-ENTRY_FOOTER + </div> +</div> + +ENTRY_FOOTER +end + +html << <<-FOOTER +</body> +</html> +FOOTER + +puts html diff --git a/tools/examples/svnlook.py b/tools/examples/svnlook.py new file mode 100755 index 0000000..72043e6 --- /dev/null +++ b/tools/examples/svnlook.py @@ -0,0 +1,441 @@ +#!/usr/bin/env python +# +# svnlook.py : a Python-based replacement for svnlook +# +###################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### +# + +import sys +import time +import os + +from svn import core, fs, delta, repos + +class SVNLook: + def __init__(self, path, cmd, rev, txn): + path = core.svn_path_canonicalize(path) + repos_ptr = repos.open(path) + self.fs_ptr = repos.fs(repos_ptr) + + if txn: + self.txn_ptr = fs.open_txn(self.fs_ptr, txn) + else: + self.txn_ptr = None + if rev is None: + rev = fs.youngest_rev(self.fs_ptr) + self.rev = rev + + getattr(self, 'cmd_' + cmd)() + + def cmd_default(self): + self.cmd_info() + self.cmd_tree() + + def cmd_author(self): + # get the author property, or empty string if the property is not present + author = self._get_property(core.SVN_PROP_REVISION_AUTHOR) or '' + print(author) + + def cmd_changed(self): + self._print_tree(ChangedEditor, pass_root=1) + + def cmd_date(self): + if self.txn_ptr: + print("") + else: + date = self._get_property(core.SVN_PROP_REVISION_DATE) + if date: + aprtime = core.svn_time_from_cstring(date) + # ### convert to a time_t; this requires intimate knowledge of + # ### the apr_time_t type + secs = aprtime / 1000000 # aprtime is microseconds; make seconds + + # assume secs in local TZ, convert to tuple, and format + ### we don't really know the TZ, do we? + print(time.strftime('%Y-%m-%d %H:%M', time.localtime(secs))) + else: + print("") + + def cmd_diff(self): + self._print_tree(DiffEditor, pass_root=1) + + def cmd_dirs_changed(self): + self._print_tree(DirsChangedEditor) + + def cmd_ids(self): + self._print_tree(Editor, base_rev=0, pass_root=1) + + def cmd_info(self): + self.cmd_author() + self.cmd_date() + self.cmd_log(1) + + def cmd_log(self, print_size=0): + # get the log property, or empty string if the property is not present + log = self._get_property(core.SVN_PROP_REVISION_LOG) or '' + if print_size: + print(len(log)) + print(log) + + def cmd_tree(self): + self._print_tree(Editor, base_rev=0) + + def _get_property(self, name): + if self.txn_ptr: + return fs.txn_prop(self.txn_ptr, name) + return fs.revision_prop(self.fs_ptr, self.rev, name) + + def _print_tree(self, e_factory, base_rev=None, pass_root=0): + if base_rev is None: + # a specific base rev was not provided. use the transaction base, + # or the previous revision + if self.txn_ptr: + base_rev = fs.txn_base_revision(self.txn_ptr) + else: + base_rev = self.rev - 1 + + # get the current root + if self.txn_ptr: + root = fs.txn_root(self.txn_ptr) + else: + root = fs.revision_root(self.fs_ptr, self.rev) + + # the base of the comparison + base_root = fs.revision_root(self.fs_ptr, base_rev) + + if pass_root: + editor = e_factory(root, base_root) + else: + editor = e_factory() + + # construct the editor for printing these things out + e_ptr, e_baton = delta.make_editor(editor) + + # compute the delta, printing as we go + def authz_cb(root, path, pool): + return 1 + repos.dir_delta(base_root, '', '', root, '', + e_ptr, e_baton, authz_cb, 0, 1, 0, 0) + + +class Editor(delta.Editor): + def __init__(self, root=None, base_root=None): + self.root = root + # base_root ignored + + self.indent = '' + + def open_root(self, base_revision, dir_pool): + print('/' + self._get_id('/')) + self.indent = self.indent + ' ' # indent one space + + def add_directory(self, path, *args): + id = self._get_id(path) + print(self.indent + _basename(path) + '/' + id) + self.indent = self.indent + ' ' # indent one space + + # we cheat. one method implementation for two entry points. + open_directory = add_directory + + def close_directory(self, baton): + # note: if indents are being performed, this slice just returns + # another empty string. + self.indent = self.indent[:-1] + + def add_file(self, path, *args): + id = self._get_id(path) + print(self.indent + _basename(path) + id) + + # we cheat. one method implementation for two entry points. + open_file = add_file + + def _get_id(self, path): + if self.root: + id = fs.node_id(self.root, path) + return ' <%s>' % fs.unparse_id(id) + return '' + +class DirsChangedEditor(delta.Editor): + def open_root(self, base_revision, dir_pool): + return [ 1, '' ] + + def delete_entry(self, path, revision, parent_baton, pool): + self._dir_changed(parent_baton) + + def add_directory(self, path, parent_baton, + copyfrom_path, copyfrom_revision, dir_pool): + self._dir_changed(parent_baton) + return [ 1, path ] + + def open_directory(self, path, parent_baton, base_revision, dir_pool): + return [ 1, path ] + + def change_dir_prop(self, dir_baton, name, value, pool): + self._dir_changed(dir_baton) + + def add_file(self, path, parent_baton, + copyfrom_path, copyfrom_revision, file_pool): + self._dir_changed(parent_baton) + + def open_file(self, path, parent_baton, base_revision, file_pool): + # some kind of change is going to happen + self._dir_changed(parent_baton) + + def _dir_changed(self, baton): + if baton[0]: + # the directory hasn't been printed yet. do it. + print(baton[1] + '/') + baton[0] = 0 + +class ChangedEditor(delta.Editor): + def __init__(self, root, base_root): + self.root = root + self.base_root = base_root + + def open_root(self, base_revision, dir_pool): + return [ 1, '' ] + + def delete_entry(self, path, revision, parent_baton, pool): + ### need more logic to detect 'replace' + if fs.is_dir(self.base_root, '/' + path): + print('D ' + path + '/') + else: + print('D ' + path) + + def add_directory(self, path, parent_baton, + copyfrom_path, copyfrom_revision, dir_pool): + print('A ' + path + '/') + return [ 0, path ] + + def open_directory(self, path, parent_baton, base_revision, dir_pool): + return [ 1, path ] + + def change_dir_prop(self, dir_baton, name, value, pool): + if dir_baton[0]: + # the directory hasn't been printed yet. do it. + print('_U ' + dir_baton[1] + '/') + dir_baton[0] = 0 + + def add_file(self, path, parent_baton, + copyfrom_path, copyfrom_revision, file_pool): + print('A ' + path) + return [ '_', ' ', None ] + + def open_file(self, path, parent_baton, base_revision, file_pool): + return [ '_', ' ', path ] + + def apply_textdelta(self, file_baton, base_checksum): + file_baton[0] = 'U' + + # no handler + return None + + def change_file_prop(self, file_baton, name, value, pool): + file_baton[1] = 'U' + + def close_file(self, file_baton, text_checksum): + text_mod, prop_mod, path = file_baton + # test the path. it will be None if we added this file. + if path: + status = text_mod + prop_mod + # was there some kind of change? + if status != '_ ': + print(status + ' ' + path) + + +class DiffEditor(delta.Editor): + def __init__(self, root, base_root): + self.root = root + self.base_root = base_root + self.target_revision = 0 + + def _do_diff(self, base_path, path): + if base_path is None: + print("Added: " + path) + label = path + elif path is None: + print("Removed: " + base_path) + label = base_path + else: + print("Modified: " + path) + label = path + print("===============================================================" + \ + "===============") + args = [] + args.append("-L") + args.append(label + "\t(original)") + args.append("-L") + args.append(label + "\t(new)") + args.append("-u") + differ = fs.FileDiff(self.base_root, base_path, self.root, + path, diffoptions=args) + pobj = differ.get_pipe() + while True: + line = pobj.readline() + if not line: + break + sys.stdout.write("%s " % line) + print("") + + def _do_prop_diff(self, path, prop_name, prop_val, pool): + print("Property changes on: " + path) + print("_______________________________________________________________" + \ + "_______________") + + old_prop_val = None + + try: + old_prop_val = fs.node_prop(self.base_root, path, prop_name, pool) + except core.SubversionException: + pass # Must be a new path + + if old_prop_val: + if prop_val: + print("Modified: " + prop_name) + print(" - " + str(old_prop_val)) + print(" + " + str(prop_val)) + else: + print("Deleted: " + prop_name) + print(" - " + str(old_prop_val)) + else: + print("Added: " + prop_name) + print(" + " + str(prop_val)) + + print("") + + def delete_entry(self, path, revision, parent_baton, pool): + ### need more logic to detect 'replace' + if not fs.is_dir(self.base_root, '/' + path): + self._do_diff(path, None) + + def add_directory(self, path, parent_baton, copyfrom_path, + copyfrom_revision, dir_pool): + return [ 1, path ] + + def add_file(self, path, parent_baton, + copyfrom_path, copyfrom_revision, file_pool): + self._do_diff(None, path) + return [ '_', ' ', None ] + + def open_root(self, base_revision, dir_pool): + return [ 1, '' ] + + def open_directory(self, path, parent_baton, base_revision, dir_pool): + return [ 1, path ] + + def open_file(self, path, parent_baton, base_revision, file_pool): + return [ '_', ' ', path ] + + def apply_textdelta(self, file_baton, base_checksum): + if file_baton[2] is not None: + self._do_diff(file_baton[2], file_baton[2]) + return None + + def change_file_prop(self, file_baton, name, value, pool): + if file_baton[2] is not None: + self._do_prop_diff(file_baton[2], name, value, pool) + return None + + def change_dir_prop(self, dir_baton, name, value, pool): + if dir_baton[1] is not None: + self._do_prop_diff(dir_baton[1], name, value, pool) + return None + + def set_target_revision(self, target_revision): + self.target_revision = target_revision + +def _basename(path): + "Return the basename for a '/'-separated path." + idx = path.rfind('/') + if idx == -1: + return path + return path[idx+1:] + + +def usage(exit): + if exit: + output = sys.stderr + else: + output = sys.stdout + + output.write( + "usage: %s REPOS_PATH rev REV [COMMAND] - inspect revision REV\n" + " %s REPOS_PATH txn TXN [COMMAND] - inspect transaction TXN\n" + " %s REPOS_PATH [COMMAND] - inspect the youngest revision\n" + "\n" + "REV is a revision number > 0.\n" + "TXN is a transaction name.\n" + "\n" + "If no command is given, the default output (which is the same as\n" + "running the subcommands `info' then `tree') will be printed.\n" + "\n" + "COMMAND can be one of: \n" + "\n" + " author: print author.\n" + " changed: print full change summary: all dirs & files changed.\n" + " date: print the timestamp (revisions only).\n" + " diff: print GNU-style diffs of changed files and props.\n" + " dirs-changed: print changed directories.\n" + " ids: print the tree, with nodes ids.\n" + " info: print the author, data, log_size, and log message.\n" + " log: print log message.\n" + " tree: print the tree.\n" + "\n" + % (sys.argv[0], sys.argv[0], sys.argv[0])) + + sys.exit(exit) + +def main(): + if len(sys.argv) < 2: + usage(1) + + rev = txn = None + + args = sys.argv[2:] + if args: + cmd = args[0] + if cmd == 'rev': + if len(args) == 1: + usage(1) + try: + rev = int(args[1]) + except ValueError: + usage(1) + del args[:2] + elif cmd == 'txn': + if len(args) == 1: + usage(1) + txn = args[1] + del args[:2] + + if args: + if len(args) > 1: + usage(1) + cmd = args[0].replace('-', '_') + else: + cmd = 'default' + + if not hasattr(SVNLook, 'cmd_' + cmd): + usage(1) + + SVNLook(sys.argv[1], cmd, rev, txn) + +if __name__ == '__main__': + main() diff --git a/tools/examples/svnlook.rb b/tools/examples/svnlook.rb new file mode 100755 index 0000000..a48dcca --- /dev/null +++ b/tools/examples/svnlook.rb @@ -0,0 +1,516 @@ +#!/usr/bin/env ruby +# +# svnlook.rb : a Ruby-based replacement for svnlook +# +###################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### +# + +require "svn/core" +require "svn/fs" +require "svn/delta" +require "svn/repos" + +# Chomp off trailing slashes +def basename(path) + path.chomp("/") +end + +# SvnLook: a Ruby-based replacement for svnlook +class SvnLook + + # Initialize the SvnLook application + def initialize(path, rev, txn) + # Open a repository + @fs = Svn::Repos.open(basename(path)).fs + + # If a transaction was specified, open it + if txn + @txn = @fs.open_txn(txn) + else + # Use the latest revision from the repo, + # if they haven't specified a revision + @txn = nil + rev ||= @fs.youngest_rev + end + + @rev = rev + end + + # Dispatch all commands to appropriate subroutines + def run(cmd, *args) + dispatch(cmd, *args) + end + + private + + # Dispatch all commands to appropriate subroutines + def dispatch(cmd, *args) + if respond_to?("cmd_#{cmd}", true) + begin + __send__("cmd_#{cmd}", *args) + rescue ArgumentError + puts $!.message + puts $@ + puts("invalid argument for #{cmd}: #{args.join(' ')}") + end + else + puts("unknown command: #{cmd}") + end + end + + # Default command: Run the 'info' and 'tree' commands + def cmd_default + cmd_info + cmd_tree + end + + # Print the 'author' of the specified revision or transaction + def cmd_author + puts(property(Svn::Core::PROP_REVISION_AUTHOR) || "") + end + + # Not implemented yet + def cmd_cat + end + + # Find out what has changed in the specified revision or transaction + def cmd_changed + print_tree(ChangedEditor, nil, true) + end + + # Output the date that the current revision was committed. + def cmd_date + if @txn + # It's not committed yet, so output nothing + puts + else + # Get the time the revision was committed + date = property(Svn::Core::PROP_REVISION_DATE) + + if date + # Print out the date in a nice format + puts date.strftime('%Y-%m-%d %H:%M(%Z)') + else + # The specified revision doesn't have an associated date. + # Output just a blank line. + puts + end + end + end + + # Output what changed in the specified revision / transaction + def cmd_diff + print_tree(DiffEditor, nil, true) + end + + # Output what directories changed in the specified revision / transaction + def cmd_dirs_changed + print_tree(DirsChangedEditor) + end + + # Output the tree, with node ids + def cmd_ids + print_tree(Editor, 0, true) + end + + # Output the author, date, and the log associated with the specified + # revision / transaction + def cmd_info + cmd_author + cmd_date + cmd_log(true) + end + + # Output the log message associated with the specified revision / transaction + def cmd_log(print_size=false) + log = property(Svn::Core::PROP_REVISION_LOG) || '' + puts log.length if print_size + puts log + end + + # Output the tree associated with the provided tree + def cmd_tree + print_tree(Editor, 0) + end + + # Output the repository's UUID. + def cmd_uuid + puts @fs.uuid + end + + # Output the repository's youngest revision. + def cmd_youngest + puts @fs.youngest_rev + end + + # Return a property of the specified revision or transaction. + # Name: the ID of the property you want to retrieve. + # E.g. Svn::Core::PROP_REVISION_LOG + def property(name) + if @txn + @txn.prop(name) + else + @fs.prop(name, @rev) + end + end + + # Print a tree of differences between two revisions + def print_tree(editor_class, base_rev=nil, pass_root=false) + if base_rev.nil? + if @txn + # Output changes since the base revision of the transaction + base_rev = @txn.base_revision + else + # Output changes since the previous revision + base_rev = @rev - 1 + end + end + + # Get the root of the specified transaction or revision + if @txn + root = @txn.root + else + root = @fs.root(@rev) + end + + # Get the root of the base revision + base_root = @fs.root(base_rev) + + # Does the provided editor need to know + # the revision and base revision we're working with? + if pass_root + # Create a new editor with the provided root and base_root + editor = editor_class.new(root, base_root) + else + # Create a new editor with nil root and base_roots + editor = editor_class.new + end + + # Do a directory delta between the two roots with + # the specified editor + base_root.dir_delta('', '', root, '', editor) + end + + # Output the current tree for a specified revision + class Editor < Svn::Delta::BaseEditor + + # Initialize the Editor object + def initialize(root=nil, base_root=nil) + @root = root + # base_root ignored + + @indent = "" + end + + # Recurse through the root (and increase the indent level) + def open_root(base_revision) + puts "/#{id('/')}" + @indent << ' ' + end + + # If a directory is added, output this and increase + # the indent level + def add_directory(path, *args) + puts "#{@indent}#{basename(path)}/#{id(path)}" + @indent << ' ' + end + + alias open_directory add_directory + + # If a directory is closed, reduce the ident level + def close_directory(baton) + @indent.chop! + end + + # If a file is added, output that it has been changed + def add_file(path, *args) + puts "#{@indent}#{basename(path)}#{id(path)}" + end + + alias open_file add_file + + # Private methods + private + + # Get the node id of a particular path + def id(path) + if @root + fs_id = @root.node_id(path) + " <#{fs_id.unparse}>" + else + "" + end + end + end + + + # Output directories that have been changed. + # In this class, methods such as open_root and add_file + # are inherited from Svn::Delta::ChangedDirsEditor. + class DirsChangedEditor < Svn::Delta::ChangedDirsEditor + + # Private functions + private + + # Print out the name of a directory if it has been changed. + # But only do so once. + # This behaves in a way like a callback function does. + def dir_changed(baton) + if baton[0] + # The directory hasn't been printed yet, + # so print it out. + puts baton[1] + '/' + + # Make sure we don't print this directory out twice + baton[0] = nil + end + end + end + + # Output files that have been changed between two roots + class ChangedEditor < Svn::Delta::BaseEditor + + # Constructor + def initialize(root, base_root) + @root = root + @base_root = base_root + end + + # Look at the root node + def open_root(base_revision) + # Nothing has been printed out yet, so return 'true'. + [true, ''] + end + + # Output deleted files + def delete_entry(path, revision, parent_baton) + # Output deleted paths with a D in front of them + print "D #{path}" + + # If we're deleting a directory, + # indicate this with a trailing slash + if @base_root.dir?('/' + path) + puts "/" + else + puts + end + end + + # Output that a directory has been added + def add_directory(path, parent_baton, + copyfrom_path, copyfrom_revision) + # Output 'A' to indicate that the directory was added. + # Also put a trailing slash since it's a directory. + puts "A #{path}/" + + # The directory has been printed -- don't print it again. + [false, path] + end + + # Recurse inside directories + def open_directory(path, parent_baton, base_revision) + # Nothing has been printed out yet, so return true. + [true, path] + end + + def change_dir_prop(dir_baton, name, value) + # Has the directory been printed yet? + if dir_baton[0] + # Print the directory + puts "_U #{dir_baton[1]}/" + + # Don't let this directory get printed again. + dir_baton[0] = false + end + end + + def add_file(path, parent_baton, + copyfrom_path, copyfrom_revision) + # Output that a directory has been added + puts "A #{path}" + + # We've already printed out this entry, so return '_' + # to prevent it from being printed again + ['_', ' ', nil] + end + + + def open_file(path, parent_baton, base_revision) + # Changes have been made -- return '_' to indicate as such + ['_', ' ', path] + end + + def apply_textdelta(file_baton, base_checksum) + # The file has been changed -- we'll print that out later. + file_baton[0] = 'U' + nil + end + + def change_file_prop(file_baton, name, value) + # The file has been changed -- we'll print that out later. + file_baton[1] = 'U' + end + + def close_file(file_baton, text_checksum) + text_mod, prop_mod, path = file_baton + # Test the path. It will be nil if we added this file. + if path + status = text_mod + prop_mod + # Was there some kind of change? + if status != '_ ' + puts "#{status} #{path}" + end + end + end + end + + # Output diffs of files that have been changed + class DiffEditor < Svn::Delta::BaseEditor + + # Constructor + def initialize(root, base_root) + @root = root + @base_root = base_root + end + + # Handle deleted files and directories + def delete_entry(path, revision, parent_baton) + # Print out diffs of deleted files, but not + # deleted directories + unless @base_root.dir?('/' + path) + do_diff(path, nil) + end + end + + # Handle added files + def add_file(path, parent_baton, + copyfrom_path, copyfrom_revision) + # If a file has been added, print out the diff. + do_diff(nil, path) + + ['_', ' ', nil] + end + + # Handle files + def open_file(path, parent_baton, base_revision) + ['_', ' ', path] + end + + # If a file is changed, print out the diff + def apply_textdelta(file_baton, base_checksum) + if file_baton[2].nil? + nil + else + do_diff(file_baton[2], file_baton[2]) + end + end + + private + + # Print out a diff between two paths + def do_diff(base_path, path) + if base_path.nil? + # If there's no base path, then the file + # must have been added + puts("Added: #{path}") + name = path + elsif path.nil? + # If there's no new path, then the file + # must have been deleted + puts("Removed: #{base_path}") + name = base_path + else + # Otherwise, the file must have been modified + puts "Modified: #{path}" + name = path + end + + # Set up labels for the two files + base_label = "#{name} (original)" + label = "#{name} (new)" + + # Output a unified diff between the two files + puts "=" * 78 + differ = Svn::Fs::FileDiff.new(@base_root, base_path, @root, path) + puts differ.unified(base_label, label) + puts + end + end +end + +# Output usage message and exit +def usage + messages = [ + "usage: #{$0} REPOS_PATH rev REV [COMMAND] - inspect revision REV", + " #{$0} REPOS_PATH txn TXN [COMMAND] - inspect transaction TXN", + " #{$0} REPOS_PATH [COMMAND] - inspect the youngest revision", + "", + "REV is a revision number > 0.", + "TXN is a transaction name.", + "", + "If no command is given, the default output (which is the same as", + "running the subcommands `info' then `tree') will be printed.", + "", + "COMMAND can be one of: ", + "", + " author: print author.", + " changed: print full change summary: all dirs & files changed.", + " date: print the timestamp (revisions only).", + " diff: print GNU-style diffs of changed files and props.", + " dirs-changed: print changed directories.", + " ids: print the tree, with nodes ids.", + " info: print the author, data, log_size, and log message.", + " log: print log message.", + " tree: print the tree.", + " uuid: print the repository's UUID (REV and TXN ignored).", + " youngest: print the youngest revision number (REV and TXN ignored).", + ] + puts(messages.join("\n")) + exit(1) +end + +# Output usage if necessary +if ARGV.empty? + usage +end + +# Process arguments +path = ARGV.shift +cmd = ARGV.shift +rev = nil +txn = nil + +case cmd +when "rev" + rev = Integer(ARGV.shift) + cmd = ARGV.shift +when "txn" + txn = ARGV.shift + cmd = ARGV.shift +end + +# If no command is specified, use the default +cmd ||= "default" + +# Replace dashes in the command with underscores +cmd = cmd.gsub(/-/, '_') + +# Start SvnLook with the specified command +SvnLook.new(path, rev, txn).run(cmd) diff --git a/tools/examples/svnput.c b/tools/examples/svnput.c new file mode 100644 index 0000000..6414fef --- /dev/null +++ b/tools/examples/svnput.c @@ -0,0 +1,352 @@ +/* + * svnput.c : upload a single file to a repository, overwriting + * any existing file by the same name. + * + * *************************************************************** + + * WARNING!! Despite the warnings it gives, this program allows + * you to potentially overwrite a file you've never seen. + * USE AT YOUR OWN RISK! + * + * (While the repository won't 'lose' overwritten data, the + * overwriting may happen without your knowledge, and has the + * potential to cause much grief with your collaborators!) + * + * *************************************************************** + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * To compile on unix against Subversion and APR libraries, try + * something like: + * + * cc svnput.c -o svnput \ + * -I/usr/local/include/subversion-1 -I/usr/local/apache2/include \ + * -L/usr/local/apache2/lib -L/usr/local/lib \ + * -lsvn_client-1 -lapr-0 -laprutil-0 + * + */ + +#include "svn_client.h" +#include "svn_pools.h" +#include "svn_config.h" +#include "svn_fs.h" +#include "svn_cmdline.h" +#include "svn_path.h" +#include "svn_time.h" + + +/* Display a prompt and read a one-line response into the provided buffer, + removing a trailing newline if present. */ +static svn_error_t * +prompt_and_read_line(const char *prompt, + char *buffer, + size_t max) +{ + int len; + printf("%s: ", prompt); + if (fgets(buffer, max, stdin) == NULL) + return svn_error_create(0, NULL, "error reading stdin"); + len = strlen(buffer); + if (len > 0 && buffer[len-1] == '\n') + buffer[len-1] = 0; + return SVN_NO_ERROR; +} + +/* A tiny callback function of type 'svn_auth_simple_prompt_func_t'. For + a much better example, see svn_cl__auth_simple_prompt in the official + svn cmdline client. */ +static svn_error_t * +my_simple_prompt_callback (svn_auth_cred_simple_t **cred, + void *baton, + const char *realm, + const char *username, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_simple_t *ret = apr_pcalloc (pool, sizeof (*ret)); + char answerbuf[100]; + + if (realm) + { + printf ("Authentication realm: %s\n", realm); + } + + if (username) + ret->username = apr_pstrdup (pool, username); + else + { + SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf))); + ret->username = apr_pstrdup (pool, answerbuf); + } + + SVN_ERR (prompt_and_read_line("Password", answerbuf, sizeof(answerbuf))); + ret->password = apr_pstrdup (pool, answerbuf); + + *cred = ret; + return SVN_NO_ERROR; +} + + +/* A tiny callback function of type 'svn_auth_username_prompt_func_t'. For + a much better example, see svn_cl__auth_username_prompt in the official + svn cmdline client. */ +static svn_error_t * +my_username_prompt_callback (svn_auth_cred_username_t **cred, + void *baton, + const char *realm, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_username_t *ret = apr_pcalloc (pool, sizeof (*ret)); + char answerbuf[100]; + + if (realm) + { + printf ("Authentication realm: %s\n", realm); + } + + SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf))); + ret->username = apr_pstrdup (pool, answerbuf); + + *cred = ret; + return SVN_NO_ERROR; +} + +/* A callback function used when the RA layer needs a handle to a + temporary file. This is a reduced version of the callback used in + the official svn cmdline client. */ +static svn_error_t * +open_tmp_file (apr_file_t **fp, + void *callback_baton, + apr_pool_t *pool) +{ + const char *path; + const char *ignored_filename; + + SVN_ERR (svn_io_temp_dir (&path, pool)); + path = svn_path_join (path, "tempfile", pool); + + /* Open a unique file, with delete-on-close set. */ + SVN_ERR (svn_io_open_unique_file2 (fp, &ignored_filename, + path, ".tmp", + svn_io_file_del_on_close, pool)); + + return SVN_NO_ERROR; +} + + +/* Called when a commit is successful. */ +static svn_error_t * +my_commit_callback (svn_revnum_t new_revision, + const char *date, + const char *author, + void *baton) +{ + printf ("Upload complete. Committed revision %ld.\n", new_revision); + return SVN_NO_ERROR; +} + + + +int +main (int argc, const char **argv) +{ + apr_pool_t *pool; + svn_error_t *err; + apr_hash_t *dirents; + const char *upload_file, *URL; + const char *parent_URL, *basename; + svn_ra_plugin_t *ra_lib; + void *session, *ra_baton; + svn_revnum_t rev; + const svn_delta_editor_t *editor; + void *edit_baton; + svn_dirent_t *dirent; + svn_ra_callbacks_t *cbtable; + apr_hash_t *cfg_hash; + svn_auth_baton_t *auth_baton; + + if (argc <= 2) + { + printf ("Usage: %s PATH URL\n", argv[0]); + printf (" Uploads file at PATH to Subversion repository URL.\n"); + return EXIT_FAILURE; + } + upload_file = argv[1]; + URL = argv[2]; + + /* Initialize the app. Send all error messages to 'stderr'. */ + if (svn_cmdline_init ("minimal_client", stderr) != EXIT_SUCCESS) + return EXIT_FAILURE; + + /* Create top-level memory pool. Be sure to read the HACKING file to + understand how to properly use/free subpools. */ + pool = svn_pool_create (NULL); + + /* Initialize the FS library. */ + err = svn_fs_initialize (pool); + if (err) goto hit_error; + + /* Make sure the ~/.subversion run-time config files exist, and load. */ + err = svn_config_ensure (NULL, pool); + if (err) goto hit_error; + + err = svn_config_get_config (&cfg_hash, NULL, pool); + if (err) goto hit_error; + + /* Build an authentication baton. */ + { + /* There are many different kinds of authentication back-end + "providers". See svn_auth.h for a full overview. */ + svn_auth_provider_object_t *provider; + apr_array_header_t *providers + = apr_array_make (pool, 4, sizeof (svn_auth_provider_object_t *)); + + svn_client_get_simple_prompt_provider (&provider, + my_simple_prompt_callback, + NULL, /* baton */ + 2, /* retry limit */ pool); + APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; + + svn_client_get_username_prompt_provider (&provider, + my_username_prompt_callback, + NULL, /* baton */ + 2, /* retry limit */ pool); + APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; + + /* Register the auth-providers into the context's auth_baton. */ + svn_auth_open (&auth_baton, providers, pool); + } + + /* Create a table of callbacks for the RA session, mostly nonexistent. */ + cbtable = apr_pcalloc (pool, sizeof(*cbtable)); + cbtable->auth_baton = auth_baton; + cbtable->open_tmp_file = open_tmp_file; + + /* Now do the real work. */ + + /* Open an RA session to the parent URL, fetch current HEAD rev and + "lock" onto that revnum for the remainder of the session. */ + svn_path_split (URL, &parent_URL, &basename, pool); + + err = svn_ra_init_ra_libs (&ra_baton, pool); + if (err) goto hit_error; + + err = svn_ra_get_ra_library (&ra_lib, ra_baton, parent_URL, pool); + if (err) goto hit_error; + + err = ra_lib->open (&session, parent_URL, cbtable, NULL, cfg_hash, pool); + if (err) goto hit_error; + + err = ra_lib->get_latest_revnum (session, &rev, pool); + if (err) goto hit_error; + + /* Examine contents of parent dir in the rev. */ + err = ra_lib->get_dir (session, "", rev, &dirents, NULL, NULL, pool); + if (err) goto hit_error; + + /* Sanity checks. Don't let the user shoot himself *too* much. */ + dirent = apr_hash_get (dirents, basename, APR_HASH_KEY_STRING); + if (dirent && dirent->kind == svn_node_dir) + { + printf ("Sorry, a directory already exists at that URL.\n"); + return EXIT_FAILURE; + } + if (dirent && dirent->kind == svn_node_file) + { + char answer[5]; + + printf ("\n*** WARNING ***\n\n"); + printf ("You're about to overwrite r%ld of this file.\n", rev); + printf ("It was last changed by user '%s',\n", + dirent->last_author ? dirent->last_author : "?"); + printf ("on %s.\n", svn_time_to_human_cstring (dirent->time, pool)); + printf ("\nSomebody *might* have just changed the file seconds ago,\n" + "and your upload would be overwriting their changes!\n\n"); + + err = prompt_and_read_line("Are you SURE you want to upload? [y/n]", + answer, sizeof(answer)); + if (err) goto hit_error; + + if (apr_strnatcasecmp (answer, "y")) + { + printf ("Operation aborted.\n"); + return EXIT_SUCCESS; + } + } + + /* Fetch a commit editor (it's anchored on the parent URL, because + the session is too.) */ + /* ### someday add an option for a user-written commit message? */ + err = ra_lib->get_commit_editor (session, &editor, &edit_baton, + "File upload from 'svnput' program.", + my_commit_callback, NULL, pool); + if (err) goto hit_error; + + /* Drive the editor */ + { + void *root_baton, *file_baton, *handler_baton; + svn_txdelta_window_handler_t handler; + svn_stream_t *contents; + apr_file_t *f = NULL; + + err = editor->open_root (edit_baton, rev, pool, &root_baton); + if (err) goto hit_error; + + if (! dirent) + { + err = editor->add_file (basename, root_baton, NULL, SVN_INVALID_REVNUM, + pool, &file_baton); + } + else + { + err = editor->open_file (basename, root_baton, rev, pool, + &file_baton); + } + if (err) goto hit_error; + + err = editor->apply_textdelta (file_baton, NULL, pool, + &handler, &handler_baton); + if (err) goto hit_error; + + err = svn_io_file_open (&f, upload_file, APR_READ, APR_OS_DEFAULT, pool); + if (err) goto hit_error; + + contents = svn_stream_from_aprfile (f, pool); + err = svn_txdelta_send_stream (contents, handler, handler_baton, + NULL, pool); + if (err) goto hit_error; + + err = svn_io_file_close (f, pool); + if (err) goto hit_error; + + err = editor->close_file (file_baton, NULL, pool); + if (err) goto hit_error; + + err = editor->close_edit (edit_baton, pool); + if (err) goto hit_error; + } + + return EXIT_SUCCESS; + + hit_error: + svn_handle_error2 (err, stderr, FALSE, "svnput: "); + return EXIT_FAILURE; +} diff --git a/tools/examples/svnserve-sgid.c b/tools/examples/svnserve-sgid.c new file mode 100644 index 0000000..29c7272 --- /dev/null +++ b/tools/examples/svnserve-sgid.c @@ -0,0 +1,60 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * Wrapper to run the svnserve process setgid. + * The idea is to avoid the problem that some interpreters like bash + * invoked by svnserve in hook scripts will reset the effective gid to + * the real gid, nuking the effect of an ordinary setgid svnserve binary. + * Sadly, to set the real gid portably, you need to be root, if only + * for a moment. + * Also smashes the environment to something known, so that games + * can't be played to try to break the security of the hook scripts, + * by setting IFS, PATH, and similar means. + */ +/* + * Written by Perry Metzger, and placed into the public domain. + */ + +#include <stdio.h> +#include <unistd.h> + +#define REAL_PATH "/usr/bin/svnserve.real" + +char *newenv[] = { "PATH=/bin:/usr/bin", "SHELL=/bin/sh", NULL }; + +int +main(int argc, char **argv) +{ + if (setgid(getegid()) == -1) { + perror("setgid(getegid())"); + return 1; + } + + if (seteuid(getuid()) == -1) { + perror("seteuid(getuid())"); + return 1; + } + + execve(REAL_PATH, argv, newenv); + perror("attempting to exec " REAL_PATH " failed"); + return 1; +} diff --git a/tools/examples/svnshell.py b/tools/examples/svnshell.py new file mode 100755 index 0000000..9c67af4 --- /dev/null +++ b/tools/examples/svnshell.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python +# +# svnshell.py : a Python-based shell interface for cruising 'round in +# the filesystem. +# +###################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### +# + +import sys +import time +import re +from cmd import Cmd +from random import randint +from svn import fs, core, repos + + +class SVNShell(Cmd): + def __init__(self, path): + """initialize an SVNShell object""" + Cmd.__init__(self) + path = core.svn_path_canonicalize(path) + self.fs_ptr = repos.fs(repos.open(path)) + self.is_rev = 1 + self.rev = fs.youngest_rev(self.fs_ptr) + self.txn = None + self.root = fs.revision_root(self.fs_ptr, self.rev) + self.path = "/" + self._setup_prompt() + self.cmdloop() + + def precmd(self, line): + if line == "EOF": + # Ctrl-D is a command without a newline. Print a newline, so the next + # shell prompt is not on the same line as the last svnshell prompt. + print("") + return "exit" + return line + + def postcmd(self, stop, line): + self._setup_prompt() + + _errors = ["Huh?", + "Whatchoo talkin' 'bout, Willis?", + "Say what?", + "Nope. Not gonna do it.", + "Ehh...I don't think so, chief."] + + def default(self, line): + print(self._errors[randint(0, len(self._errors) - 1)]) + + def do_cat(self, arg): + """dump the contents of a file""" + if not len(arg): + print("You must supply a file path.") + return + catpath = self._parse_path(arg) + kind = fs.check_path(self.root, catpath) + if kind == core.svn_node_none: + print("Path '%s' does not exist." % catpath) + return + if kind == core.svn_node_dir: + print("Path '%s' is not a file." % catpath) + return + ### be nice to get some paging in here. + stream = fs.file_contents(self.root, catpath) + while True: + data = core.svn_stream_read(stream, core.SVN_STREAM_CHUNK_SIZE) + sys.stdout.write(data) + if len(data) < core.SVN_STREAM_CHUNK_SIZE: + break + + def do_cd(self, arg): + """change directory""" + newpath = self._parse_path(arg) + + # make sure that path actually exists in the filesystem as a directory + kind = fs.check_path(self.root, newpath) + if kind != core.svn_node_dir: + print("Path '%s' is not a valid filesystem directory." % newpath) + return + self.path = newpath + + def do_ls(self, arg): + """list the contents of the current directory or provided path""" + parent = self.path + if not len(arg): + # no arg -- show a listing for the current directory. + entries = fs.dir_entries(self.root, self.path) + else: + # arg? show a listing of that path. + newpath = self._parse_path(arg) + kind = fs.check_path(self.root, newpath) + if kind == core.svn_node_dir: + parent = newpath + entries = fs.dir_entries(self.root, parent) + elif kind == core.svn_node_file: + parts = self._path_to_parts(newpath) + name = parts.pop(-1) + parent = self._parts_to_path(parts) + print(parent + ':' + name) + tmpentries = fs.dir_entries(self.root, parent) + if not tmpentries.get(name, None): + return + entries = {} + entries[name] = tmpentries[name] + else: + print("Path '%s' not found." % newpath) + return + + keys = sorted(entries.keys()) + + print(" REV AUTHOR NODE-REV-ID SIZE DATE NAME") + print("----------------------------------------------------------------------------") + + for entry in keys: + fullpath = parent + '/' + entry + size = '' + is_dir = fs.is_dir(self.root, fullpath) + if is_dir: + name = entry + '/' + else: + size = str(fs.file_length(self.root, fullpath)) + name = entry + node_id = fs.unparse_id(entries[entry].id) + created_rev = fs.node_created_rev(self.root, fullpath) + author = fs.revision_prop(self.fs_ptr, created_rev, + core.SVN_PROP_REVISION_AUTHOR) + if not author: + author = "" + date = fs.revision_prop(self.fs_ptr, created_rev, + core.SVN_PROP_REVISION_DATE) + if not date: + date = "" + else: + date = self._format_date(date) + + print("%6s %8s %12s %8s %12s %s" % (created_rev, author[:8], + node_id, size, date, name)) + + def do_lstxns(self, arg): + """list the transactions available for browsing""" + txns = sorted(fs.list_transactions(self.fs_ptr)) + counter = 0 + for txn in txns: + counter = counter + 1 + sys.stdout.write("%8s " % txn) + if counter == 6: + print("") + counter = 0 + print("") + + def do_pcat(self, arg): + """list the properties of a path""" + catpath = self.path + if len(arg): + catpath = self._parse_path(arg) + kind = fs.check_path(self.root, catpath) + if kind == core.svn_node_none: + print("Path '%s' does not exist." % catpath) + return + plist = fs.node_proplist(self.root, catpath) + if not plist: + return + for pkey, pval in plist.items(): + print('K ' + str(len(pkey))) + print(pkey) + print('P ' + str(len(pval))) + print(pval) + print('PROPS-END') + + def do_setrev(self, arg): + """set the current revision to view""" + try: + if arg.lower() == 'head': + rev = fs.youngest_rev(self.fs_ptr) + else: + rev = int(arg) + newroot = fs.revision_root(self.fs_ptr, rev) + except: + print("Error setting the revision to '" + arg + "'.") + return + fs.close_root(self.root) + self.root = newroot + self.rev = rev + self.is_rev = 1 + self._do_path_landing() + + def do_settxn(self, arg): + """set the current transaction to view""" + try: + txnobj = fs.open_txn(self.fs_ptr, arg) + newroot = fs.txn_root(txnobj) + except: + print("Error setting the transaction to '" + arg + "'.") + return + fs.close_root(self.root) + self.root = newroot + self.txn = arg + self.is_rev = 0 + self._do_path_landing() + + def do_youngest(self, arg): + """list the youngest revision available for browsing""" + rev = fs.youngest_rev(self.fs_ptr) + print(rev) + + def do_exit(self, arg): + sys.exit(0) + + def _path_to_parts(self, path): + return [_f for _f in path.split('/') if _f] + + def _parts_to_path(self, parts): + return '/' + '/'.join(parts) + + def _parse_path(self, path): + # cleanup leading, trailing, and duplicate '/' characters + newpath = self._parts_to_path(self._path_to_parts(path)) + + # if PATH is absolute, use it, else append it to the existing path. + if path.startswith('/') or self.path == '/': + newpath = '/' + newpath + else: + newpath = self.path + '/' + newpath + + # cleanup '.' and '..' + parts = self._path_to_parts(newpath) + finalparts = [] + for part in parts: + if part == '.': + pass + elif part == '..': + if len(finalparts) != 0: + finalparts.pop(-1) + else: + finalparts.append(part) + + # finally, return the calculated path + return self._parts_to_path(finalparts) + + def _format_date(self, date): + date = core.svn_time_from_cstring(date) + date = time.asctime(time.localtime(date / 1000000)) + return date[4:-8] + + def _do_path_landing(self): + """try to land on self.path as a directory in root, failing up to '/'""" + not_found = 1 + newpath = self.path + while not_found: + kind = fs.check_path(self.root, newpath) + if kind == core.svn_node_dir: + not_found = 0 + else: + parts = self._path_to_parts(newpath) + parts.pop(-1) + newpath = self._parts_to_path(parts) + self.path = newpath + + def _setup_prompt(self): + """present the prompt and handle the user's input""" + if self.is_rev: + self.prompt = "<rev: " + str(self.rev) + else: + self.prompt = "<txn: " + self.txn + self.prompt += " " + self.path + ">$ " + + def _complete(self, text, line, begidx, endidx, limit_node_kind=None): + """Generic tab completer. Takes the 4 standard parameters passed to a + cmd.Cmd completer function, plus LIMIT_NODE_KIND, which should be a + svn.core.svn_node_foo constant to restrict the returned completions to, or + None for no limit. Catches and displays exceptions, because otherwise + they are silently ignored - which is quite frustrating when debugging!""" + try: + args = line.split() + if len(args) > 1: + arg = args[1] + else: + arg = "" + dirs = arg.split('/') + user_elem = dirs[-1] + user_dir = "/".join(dirs[:-1] + ['']) + + canon_dir = self._parse_path(user_dir) + + entries = fs.dir_entries(self.root, canon_dir) + acceptable_completions = [] + for name, dirent_t in entries.items(): + if not name.startswith(user_elem): + continue + if limit_node_kind and dirent_t.kind != limit_node_kind: + continue + if dirent_t.kind == core.svn_node_dir: + name += '/' + acceptable_completions.append(name) + if limit_node_kind == core.svn_node_dir or not limit_node_kind: + if user_elem in ('.', '..'): + for extraname in ('.', '..'): + if extraname.startswith(user_elem): + acceptable_completions.append(extraname + '/') + return acceptable_completions + except: + ei = sys.exc_info() + sys.stderr.write("EXCEPTION WHILST COMPLETING\n") + import traceback + traceback.print_tb(ei[2]) + sys.stderr.write("%s: %s\n" % (ei[0], ei[1])) + raise + + def complete_cd(self, text, line, begidx, endidx): + return self._complete(text, line, begidx, endidx, core.svn_node_dir) + + def complete_cat(self, text, line, begidx, endidx): + return self._complete(text, line, begidx, endidx, core.svn_node_file) + + def complete_ls(self, text, line, begidx, endidx): + return self._complete(text, line, begidx, endidx) + + def complete_pcat(self, text, line, begidx, endidx): + return self._complete(text, line, begidx, endidx) + + +def _basename(path): + "Return the basename for a '/'-separated path." + idx = path.rfind('/') + if idx == -1: + return path + return path[idx+1:] + + +def usage(exit): + if exit: + output = sys.stderr + else: + output = sys.stdout + output.write( + "usage: %s REPOS_PATH\n" + "\n" + "Once the program has started, type 'help' at the prompt for hints on\n" + "using the shell.\n" % sys.argv[0]) + sys.exit(exit) + +def main(): + if len(sys.argv) != 2: + usage(1) + + SVNShell(sys.argv[1]) + +if __name__ == '__main__': + main() diff --git a/tools/examples/svnshell.rb b/tools/examples/svnshell.rb new file mode 100755 index 0000000..3b43853 --- /dev/null +++ b/tools/examples/svnshell.rb @@ -0,0 +1,456 @@ +#!/usr/bin/env ruby +# +# svnshell.rb : a Ruby-based shell interface for cruising 'round in +# the filesystem. +# +# Usage: ruby svnshell.rb REPOS_PATH, where REPOS_PATH is a path to +# a repository on your local filesystem. +# +# NOTE: This program requires the Ruby readline extension. +# See http://wiki.rubyonrails.com/rails/show/ReadlineLibrary +# for details on how to install readline for Ruby. +# +###################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### +# + +require "readline" +require "shellwords" + +require "svn/fs" +require "svn/core" +require "svn/repos" + +# SvnShell: a Ruby-based shell interface for cruising 'round in +# the filesystem. +class SvnShell + + # A list of potential commands. This list is populated by + # the 'method_added' function (see below). + WORDS = [] + + # Check for methods that start with "do_" + # and list them as potential commands + class << self + def method_added(name) + if /^do_(.*)$/ =~ name.to_s + WORDS << $1 + end + end + end + + # Constructor for SvnShell + # + # path: The path to a Subversion repository + def initialize(path) + @repos_path = path + @path = "/" + self.rev = youngest_rev + @exited = false + end + + # Run the shell + def run + + # While the user hasn't typed 'exit' and there is still input to be read + while !@exited and buf = Readline.readline(prompt, true) + + # Parse the command line into a single command and arguments + cmd, *args = Shellwords.shellwords(buf) + + # Skip empty lines + next if /\A\s*\z/ =~ cmd.to_s + + # Open a new connection to the repo + @fs = Svn::Repos.open(@repos_path).fs + setup_root + + # Execute the specified command + dispatch(cmd, *args) + + # Find a path that exists in the current revision + @path = find_available_path + + # Close the connection to the repo + @root.close + + end + end + + # Private functions + private + + # Get the current prompt string + def prompt + + # Gather data for the prompt string + if rev_mode? + mode = "rev" + info = @rev + else + mode = "txn" + info = @txn + end + + # Return the prompt string + "<#{mode}: #{info} #{@path}>$ " + end + + # Dispatch a command to the appropriate do_* subroutine + def dispatch(cmd, *args) + + # Dispatch cmd to the appropriate do_* function + if respond_to?("do_#{cmd}", true) + begin + __send__("do_#{cmd}", *args) + rescue ArgumentError + # puts $!.message + # puts $@ + puts("Invalid argument for #{cmd}: #{args.join(' ')}") + end + else + puts("Unknown command: #{cmd}") + puts("Try one of these commands: ", WORDS.sort.join(" ")) + end + end + + # Output the contents of a file from the repository + def do_cat(path) + + # Normalize the path to an absolute path + normalized_path = normalize_path(path) + + # Check what type of node exists at the specified path + case @root.check_path(normalized_path) + when Svn::Core::NODE_NONE + puts "Path '#{normalized_path}' does not exist." + when Svn::Core::NODE_DIR + puts "Path '#{normalized_path}' is not a file." + else + # Output the file to standard out + @root.file_contents(normalized_path) do |stream| + puts stream.read(@root.file_length(normalized_path)) + end + end + end + + # Set the current directory + def do_cd(path="/") + + # Normalize the path to an absolute path + normalized_path = normalize_path(path) + + # If it's a valid directory, then set the directory + if @root.check_path(normalized_path) == Svn::Core::NODE_DIR + @path = normalized_path + else + puts "Path '#{normalized_path}' is not a valid filesystem directory." + end + end + + # List the contents of the current directory or provided paths + def do_ls(*paths) + + # Default to listing the contents of the current directory + paths << @path if paths.empty? + + # Foreach path + paths.each do |path| + + # Normalize the path to an absolute path + normalized_path = normalize_path(path) + + # Is it a directory or file? + case @root.check_path(normalized_path) + when Svn::Core::NODE_DIR + + # Output the contents of the directory + parent = normalized_path + entries = @root.dir_entries(parent) + + when Svn::Core::NODE_FILE + + # Split the path into directory and filename components + parts = path_to_parts(normalized_path) + name = parts.pop + parent = parts_to_path(parts) + + # Output the filename + puts "#{parent}:#{name}" + + # Double check that the file exists + # inside the parent directory + parent_entries = @root.dir_entries(parent) + if parent_entries[name].nil? + # Hmm. We found the file, but it doesn't exist inside + # the parent directory. That's a bit unusual. + puts "No directory entry found for '#{normalized_path}'" + next + else + # Save the path so it can be output in detail + entries = {name => parent_entries[name]} + end + else + # Path is not a directory or a file, + # so it must not exist + puts "Path '#{normalized_path}' not found." + next + end + + # Output a detailed listing of the files we found + puts " REV AUTHOR NODE-REV-ID SIZE DATE NAME" + puts "-" * 76 + + # For each entry we found... + entries.keys.sort.each do |entry| + + # Calculate the full path to the directory entry + fullpath = parent + '/' + entry + if @root.dir?(fullpath) + # If it's a directory, output an extra slash + size = '' + name = entry + '/' + else + # If it's a file, output the size of the file + size = @root.file_length(fullpath).to_i.to_s + name = entry + end + + # Output the entry + node_id = entries[entry].id.to_s + created_rev = @root.node_created_rev(fullpath) + author = @fs.prop(Svn::Core::PROP_REVISION_AUTHOR, created_rev).to_s + date = @fs.prop(Svn::Core::PROP_REVISION_DATE, created_rev) + args = [ + created_rev, author[0,8], + node_id, size, date.strftime("%b %d %H:%M(%Z)"), name + ] + puts "%6s %8s <%10s> %8s %17s %s" % args + + end + end + end + + # List all currently open transactions available for browsing + def do_lstxns + + # Get a sorted list of open transactions + txns = @fs.transactions + txns.sort + counter = 0 + + # Output the open transactions + txns.each do |txn| + counter = counter + 1 + puts "%8s " % txn + + # Every six transactions, output an extra newline + if counter == 6 + puts + counter = 0 + end + end + puts + end + + # Output the properties of a particular path + def do_pcat(path=nil) + + # Default to the current directory + catpath = path ? normalize_path(path) : @path + + # Make sure that the specified path exists + if @root.check_path(catpath) == Svn::Core::NODE_NONE + puts "Path '#{catpath}' does not exist." + return + end + + # Get the list of properties + plist = @root.node_proplist(catpath) + return if plist.nil? + + # Output each property + plist.each do |key, value| + puts "K #{key.size}" + puts key + puts "P #{value.size}" + puts value + end + + # That's all folks! + puts 'PROPS-END' + + end + + # Set the current revision to view + def do_setrev(rev) + + # Make sure the specified revision exists + begin + @fs.root(Integer(rev)).close + rescue Svn::Error + puts "Error setting the revision to '#{rev}': #{$!.message}" + return + end + + # Set the revision + self.rev = Integer(rev) + + end + + # Open an existing transaction to view + def do_settxn(name) + + # Make sure the specified transaction exists + begin + txn = @fs.open_txn(name) + txn.root.close + rescue Svn::Error + puts "Error setting the transaction to '#{name}': #{$!.message}" + return + end + + # Set the transaction + self.txn = name + + end + + # List the youngest revision available for browsing + def do_youngest + rev = @fs.youngest_rev + puts rev + end + + # Exit this program + def do_exit + @exited = true + end + + # Find the youngest revision + def youngest_rev + Svn::Repos.open(@repos_path).fs.youngest_rev + end + + # Set the current revision + def rev=(new_value) + @rev = new_value + @txn = nil + reset_root + end + + # Set the current transaction + def txn=(new_value) + @txn = new_value + reset_root + end + + # Check whether we are in 'revision-mode' + def rev_mode? + @txn.nil? + end + + # Close the current root and setup a new one + def reset_root + if @root + @root.close + setup_root + end + end + + # Setup a new root + def setup_root + if rev_mode? + @root = @fs.root(@rev) + else + @root = @fs.open_txn(name).root + end + end + + # Convert a path into its component parts + def path_to_parts(path) + path.split(/\/+/) + end + + # Join the component parts of a path into a string + def parts_to_path(parts) + normalized_parts = parts.reject{|part| part.empty?} + "/#{normalized_parts.join('/')}" + end + + # Convert a path to a normalized, absolute path + def normalize_path(path) + + # Convert the path to an absolute path + if path[0,1] != "/" and @path != "/" + path = "#{@path}/#{path}" + end + + # Split the path into its component parts + parts = path_to_parts(path) + + # Build a list of the normalized parts of the path + normalized_parts = [] + parts.each do |part| + case part + when "." + # ignore + when ".." + normalized_parts.pop + else + normalized_parts << part + end + end + + # Join the normalized parts together into a string + parts_to_path(normalized_parts) + + end + + # Find the parent directory of a specified path + def parent_dir(path) + normalize_path("#{path}/..") + end + + # Try to land on the specified path as a directory. + # If the specified path does not exist, look for + # an ancestor path that does exist. + def find_available_path(path=@path) + if @root.check_path(path) == Svn::Core::NODE_DIR + path + else + find_available_path(parent_dir(path)) + end + end + +end + + +# Autocomplete commands +Readline.completion_proc = Proc.new do |word| + SvnShell::WORDS.grep(/^#{Regexp.quote(word)}/) +end + +# Output usage information if necessary +if ARGV.size != 1 + puts "Usage: #{$0} REPOS_PATH" + exit(1) +end + +# Create a new SvnShell with the command-line arguments and run it +SvnShell.new(ARGV.shift).run diff --git a/tools/examples/testwrite.c b/tools/examples/testwrite.c new file mode 100644 index 0000000..beb2fba --- /dev/null +++ b/tools/examples/testwrite.c @@ -0,0 +1,276 @@ +/* + * testwrite.c : test whether a user has commit access. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * To compile on unix against Subversion and APR libraries, try + * something like: + * + * cc testwrite.c -o testwrite \ + * -I/usr/local/include/subversion-1 -I/usr/local/apache2/include \ + * -L/usr/local/apache2/lib -L/usr/local/lib \ + * -lsvn_client-1 -lsvn_ra-1 -lsvn_subr-1 -lsvn-fs-1 -lapr-0 -laprutil-0 + * + */ + +#include "svn_client.h" +#include "svn_pools.h" +#include "svn_config.h" +#include "svn_fs.h" +#include "svn_cmdline.h" +#include "svn_path.h" +#include "svn_time.h" + + +/* Display a prompt and read a one-line response into the provided buffer, + removing a trailing newline if present. */ +static svn_error_t * +prompt_and_read_line(const char *prompt, + char *buffer, + size_t max) +{ + int len; + printf("%s: ", prompt); + if (fgets(buffer, max, stdin) == NULL) + return svn_error_create(0, NULL, "error reading stdin"); + len = strlen(buffer); + if (len > 0 && buffer[len-1] == '\n') + buffer[len-1] = 0; + return SVN_NO_ERROR; +} + +/* A tiny callback function of type 'svn_auth_simple_prompt_func_t'. For + a much better example, see svn_cl__auth_simple_prompt in the official + svn cmdline client. */ +static svn_error_t * +my_simple_prompt_callback (svn_auth_cred_simple_t **cred, + void *baton, + const char *realm, + const char *username, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_simple_t *ret = apr_pcalloc (pool, sizeof (*ret)); + char answerbuf[100]; + + if (realm) + { + printf ("Authentication realm: %s\n", realm); + } + + if (username) + ret->username = apr_pstrdup (pool, username); + else + { + SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf))); + ret->username = apr_pstrdup (pool, answerbuf); + } + + SVN_ERR (prompt_and_read_line("Password", answerbuf, sizeof(answerbuf))); + ret->password = apr_pstrdup (pool, answerbuf); + + *cred = ret; + return SVN_NO_ERROR; +} + + +/* A tiny callback function of type 'svn_auth_username_prompt_func_t'. For + a much better example, see svn_cl__auth_username_prompt in the official + svn cmdline client. */ +static svn_error_t * +my_username_prompt_callback (svn_auth_cred_username_t **cred, + void *baton, + const char *realm, + svn_boolean_t may_save, + apr_pool_t *pool) +{ + svn_auth_cred_username_t *ret = apr_pcalloc (pool, sizeof (*ret)); + char answerbuf[100]; + + if (realm) + { + printf ("Authentication realm: %s\n", realm); + } + + SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf))); + ret->username = apr_pstrdup (pool, answerbuf); + + *cred = ret; + return SVN_NO_ERROR; +} + +/* A callback function used when the RA layer needs a handle to a + temporary file. This is a reduced version of the callback used in + the official svn cmdline client. */ +static svn_error_t * +open_tmp_file (apr_file_t **fp, + void *callback_baton, + apr_pool_t *pool) +{ + const char *path; + const char *ignored_filename; + + SVN_ERR (svn_io_temp_dir (&path, pool)); + path = svn_path_join (path, "tempfile", pool); + + /* Open a unique file, with delete-on-close set. */ + SVN_ERR (svn_io_open_unique_file2 (fp, &ignored_filename, + path, ".tmp", + svn_io_file_del_on_close, pool)); + + return SVN_NO_ERROR; +} + + +/* Called when a commit is successful. */ +static svn_error_t * +my_commit_callback (svn_revnum_t new_revision, + const char *date, + const char *author, + void *baton) +{ + printf ("Upload complete. Committed revision %ld.\n", new_revision); + return SVN_NO_ERROR; +} + + + +int +main (int argc, const char **argv) +{ + apr_pool_t *pool; + svn_error_t *err; + apr_hash_t *dirents; + const char *upload_file, *URL; + const char *parent_URL, *basename; + svn_ra_plugin_t *ra_lib; + void *session, *ra_baton; + svn_revnum_t rev; + const svn_delta_editor_t *editor; + void *edit_baton; + svn_dirent_t *dirent; + svn_ra_callbacks_t *cbtable; + apr_hash_t *cfg_hash; + svn_auth_baton_t *auth_baton; + + if (argc <= 1) + { + printf ("Usage: %s URL\n", argv[0]); + printf (" Tries to create an svn commit-transaction at URL.\n"); + return EXIT_FAILURE; + } + URL = argv[1]; + + /* Initialize the app. Send all error messages to 'stderr'. */ + if (svn_cmdline_init ("minimal_client", stderr) != EXIT_SUCCESS) + return EXIT_FAILURE; + + /* Create top-level memory pool. Be sure to read the HACKING file to + understand how to properly use/free subpools. */ + pool = svn_pool_create (NULL); + + /* Initialize the FS library. */ + err = svn_fs_initialize (pool); + if (err) goto hit_error; + + /* Make sure the ~/.subversion run-time config files exist, and load. */ + err = svn_config_ensure (NULL, pool); + if (err) goto hit_error; + + err = svn_config_get_config (&cfg_hash, NULL, pool); + if (err) goto hit_error; + + /* Build an authentication baton. */ + { + /* There are many different kinds of authentication back-end + "providers". See svn_auth.h for a full overview. */ + svn_auth_provider_object_t *provider; + apr_array_header_t *providers + = apr_array_make (pool, 4, sizeof (svn_auth_provider_object_t *)); + + svn_client_get_simple_prompt_provider (&provider, + my_simple_prompt_callback, + NULL, /* baton */ + 2, /* retry limit */ pool); + APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; + + svn_client_get_username_prompt_provider (&provider, + my_username_prompt_callback, + NULL, /* baton */ + 2, /* retry limit */ pool); + APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; + + /* Register the auth-providers into the context's auth_baton. */ + svn_auth_open (&auth_baton, providers, pool); + } + + /* Create a table of callbacks for the RA session, mostly nonexistent. */ + cbtable = apr_pcalloc (pool, sizeof(*cbtable)); + cbtable->auth_baton = auth_baton; + cbtable->open_tmp_file = open_tmp_file; + + /* Now do the real work. */ + + /* Open an RA session to the parent URL, fetch current HEAD rev and + "lock" onto that revnum for the remainder of the session. */ + svn_path_split (URL, &parent_URL, &basename, pool); + + err = svn_ra_init_ra_libs (&ra_baton, pool); + if (err) goto hit_error; + + err = svn_ra_get_ra_library (&ra_lib, ra_baton, parent_URL, pool); + if (err) goto hit_error; + + err = ra_lib->open (&session, parent_URL, cbtable, NULL, cfg_hash, pool); + if (err) goto hit_error; + + /* Fetch a commit editor (it's anchored on the parent URL, because + the session is too.) */ + /* ### someday add an option for a user-written commit message? */ + err = ra_lib->get_commit_editor (session, &editor, &edit_baton, + "File upload from 'svnput' program.", + my_commit_callback, NULL, pool); + if (err) goto hit_error; + + /* Drive the editor */ + { + void *root_baton, *file_baton, *handler_baton; + svn_txdelta_window_handler_t handler; + svn_stream_t *contents; + apr_file_t *f = NULL; + + err = editor->open_root (edit_baton, rev, pool, &root_baton); + if (err) goto hit_error; + + err = editor->abort_edit (edit_baton, pool); + if (err) goto hit_error; + } + + printf ("No problems creating commit transaction.\n"); + return EXIT_SUCCESS; + + hit_error: + { + printf("Could not open a commit transaction.\n"); + svn_handle_error2 (err, stderr, FALSE, "testwrite: "); + return EXIT_FAILURE; + } + +} |