summaryrefslogtreecommitdiff
path: root/tools/examples
diff options
context:
space:
mode:
authorLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
committerLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
commitf1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch)
tree4223b2035bf2240d681a53822808b3c7f687b905 /tools/examples
downloadsubversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz
Tarball conversion
Diffstat (limited to 'tools/examples')
-rwxr-xr-xtools/examples/SvnCLBrowse489
-rwxr-xr-xtools/examples/blame.py113
-rwxr-xr-xtools/examples/check-modified.py65
-rwxr-xr-xtools/examples/dumpprops.py88
-rwxr-xr-xtools/examples/get-location-segments.py118
-rwxr-xr-xtools/examples/getfile.py72
-rw-r--r--tools/examples/getlocks_test.c271
-rwxr-xr-xtools/examples/geturl.py47
-rw-r--r--tools/examples/headrev.c226
-rw-r--r--tools/examples/info.rb78
-rw-r--r--tools/examples/minimal_client.c285
-rwxr-xr-xtools/examples/putfile.py90
-rwxr-xr-xtools/examples/revplist.py78
-rwxr-xr-xtools/examples/svnlog2html.rb139
-rwxr-xr-xtools/examples/svnlook.py441
-rwxr-xr-xtools/examples/svnlook.rb516
-rw-r--r--tools/examples/svnput.c352
-rw-r--r--tools/examples/svnserve-sgid.c60
-rwxr-xr-xtools/examples/svnshell.py367
-rwxr-xr-xtools/examples/svnshell.rb456
-rw-r--r--tools/examples/testwrite.c276
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>&amp;File</label>
+ <object class="wxMenuItem" name="CLBMenuFileQuit">
+ <label>&amp;Quit</label>
+ <accel>CTRL+Q</accel>
+ <help>Quit SvnCLBrowse.</help>
+ </object>
+ </object>
+ <object class="wxMenu">
+ <label>&amp;Subversion</label>
+ <object class="wxMenuItem" name="CLBMenuOpsInfo">
+ <label>&amp;Info</label>
+ <help>Show information about members of the selected changelist(s).</help>
+ </object>
+ <object class="wxMenuItem" name="CLBMenuOpsMembers">
+ <label>&amp;Members</label>
+ <help>List the members of the selected changelist(s).</help>
+ </object>
+ </object>
+ <object class="wxMenu">
+ <label>&amp;Help</label>
+ <object class="wxMenuItem" name="CLBMenuHelpAbout">
+ <label>&amp;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;
+ }
+
+}