summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 15:47:16 +0100
committerLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 15:47:16 +0100
commit25335618bf8755ce6b116ee14f47f5a1f2c821e9 (patch)
treed889d7ab3f9f985d0c54c534cb8052bd2e6d7163 /tools
downloadbzr-tarball-25335618bf8755ce6b116ee14f47f5a1f2c821e9.tar.gz
Tarball conversion
Diffstat (limited to 'tools')
-rw-r--r--tools/__init__.py0
-rw-r--r--tools/bzr_epydoc14
-rw-r--r--tools/bzr_epydoc_uid.py43
-rw-r--r--tools/capture_tree.py40
-rwxr-xr-xtools/check-newsbugs.py115
-rwxr-xr-xtools/fixed-in.py172
-rwxr-xr-xtools/generate_docs.py111
-rw-r--r--tools/generate_release_notes.py137
-rw-r--r--tools/package_docs.py109
-rw-r--r--tools/package_mf.py89
-rwxr-xr-xtools/packaging/build-packages.sh13
-rwxr-xr-xtools/packaging/lp-upload-release82
-rwxr-xr-xtools/packaging/update-changelogs.sh34
-rwxr-xr-xtools/packaging/update-control.sh32
-rwxr-xr-xtools/packaging/update-packaging-branches.sh25
-rw-r--r--tools/prepare_for_latex.py173
-rw-r--r--tools/riodemo.py60
-rwxr-xr-xtools/rst2html.py55
-rwxr-xr-xtools/rst2pdf.py179
-rwxr-xr-xtools/rst2prettyhtml.py71
-rwxr-xr-xtools/subunit-sum61
-rw-r--r--tools/time_graph.py122
-rw-r--r--tools/weavemerge.sh57
-rw-r--r--tools/win32/__init__.py1
-rw-r--r--tools/win32/bazaar.url7
-rw-r--r--tools/win32/bootstrap.py77
-rw-r--r--tools/win32/build_release.py206
-rw-r--r--tools/win32/buildout-templates/bin/build-installer.bat.in108
-rw-r--r--tools/win32/buildout.cfg197
-rw-r--r--tools/win32/bzr-win32-bdist-postinstall.py145
-rw-r--r--tools/win32/bzr.iss.cog335
-rw-r--r--tools/win32/bzr_postinstall.py356
-rw-r--r--tools/win32/file_version.py38
-rw-r--r--tools/win32/info.txt6
-rw-r--r--tools/win32/ostools.py138
-rw-r--r--tools/win32/py2exe_boot_common.py40
-rw-r--r--tools/win32/run_script.py16
-rw-r--r--tools/win32/start_bzr.bat30
38 files changed, 3494 insertions, 0 deletions
diff --git a/tools/__init__.py b/tools/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/__init__.py
diff --git a/tools/bzr_epydoc b/tools/bzr_epydoc
new file mode 100644
index 0000000..434ec4b
--- /dev/null
+++ b/tools/bzr_epydoc
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+#
+# Call the command line interface for Epydoc.
+#
+
+"""Wrapper around Epydoc that just makes it understand bzr's lazy imports."""
+
+# This will enable the lazy_import compatibility code
+import bzr_epydoc_uid
+
+from epydoc.cli import cli
+cli()
+
+
diff --git a/tools/bzr_epydoc_uid.py b/tools/bzr_epydoc_uid.py
new file mode 100644
index 0000000..a9861d1
--- /dev/null
+++ b/tools/bzr_epydoc_uid.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Monkey patch to make epydoc work with bzrlib's lazy imports."""
+
+import epydoc.uid
+
+import bzrlib.lazy_import
+
+
+_ObjectUID = epydoc.uid.ObjectUID
+_ScopeReplacer = bzrlib.lazy_import.ScopeReplacer
+
+
+class ObjectUID(_ObjectUID):
+
+ def __init__(self, obj):
+ if isinstance(obj, _ScopeReplacer):
+ # The isinstance will trigger a replacement if it is a real
+ # _BzrScopeReplacer, but the local object won't know about it, so
+ # replace it locally.
+ obj = object.__getattribute__(obj, '_real_obj')
+ _ObjectUID.__init__(self, obj)
+
+
+epydoc.uid.ObjectUID = ObjectUID
+
+
+_ScopeReplacer._should_proxy = True
+
diff --git a/tools/capture_tree.py b/tools/capture_tree.py
new file mode 100644
index 0000000..4528df9
--- /dev/null
+++ b/tools/capture_tree.py
@@ -0,0 +1,40 @@
+#! /usr/bin/env python
+
+# Copyright (C) 2005 Canonical Ltd
+
+"""Print to stdout a description of the current directory,
+formatted as a Python data structure.
+
+This can be useful in tests that need to recreate directory
+contents."""
+
+import sys
+import os
+
+from bzrlib.trace import enable_default_logging
+enable_default_logging()
+from bzrlib.selftest.treeshape import capture_tree_contents
+
+def main(argv):
+ # a lame reimplementation of pformat that splits multi-line
+ # strings into concatenated string literals.
+ print '['
+ for tt in capture_tree_contents('.'):
+ assert isinstance(tt, tuple)
+ print ' (', repr(tt[0]) + ',',
+ if len(tt) == 1:
+ print '),'
+ else:
+ assert len(tt) == 2
+ val = tt[1]
+ print
+ if val == '':
+ print " ''"
+ else:
+ for valline in val.splitlines(True):
+ print ' ', repr(valline)
+ print ' ),'
+ print ']'
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/tools/check-newsbugs.py b/tools/check-newsbugs.py
new file mode 100755
index 0000000..65fec9f
--- /dev/null
+++ b/tools/check-newsbugs.py
@@ -0,0 +1,115 @@
+#!/usr/bin/python
+# Simple script that will check which bugs mentioned in NEWS
+# are not yet marked Fix Released in Launchpad
+
+import getopt, re, sys
+try:
+ from launchpadlib.launchpad import Launchpad
+ from lazr.restfulclient import errors
+except ImportError:
+ print "Please install launchpadlib from lp:launchpadlib"
+ sys.exit(1)
+try:
+ import hydrazine
+except ImportError:
+ print "Please install hydrazine from lp:hydrazine"
+ sys.exit(1)
+
+
+options, args = getopt.gnu_getopt(sys.argv, "lw", ["launchpad", 'webbrowser'])
+options = dict(options)
+
+if len(args) == 1:
+ print ("Usage: check-newsbugs [--launchpad][--webbrowser] "
+ "doc/en/release-notes/bzr-x.y.txt")
+ print "Options:"
+ print "--launchpad Print out Launchpad mail commands for closing bugs "
+ print " that are already fixed."
+ print "--webbrowser Open launchpad bug pages for bugs that are already "
+ print " fixed."
+ sys.exit(1)
+
+
+def report_notmarked(bug, task, section):
+ print
+ print "Bug %d was mentioned in NEWS but is not marked fix released:" % (bug.id, )
+ print "Launchpad title: %s" % bug.title
+ print "NEWS summary: "
+ print section
+ if "--launchpad" in options or "-l" in options:
+ print " bug %d" % bug.id
+ print " affects %s" % task.bug_target_name
+ print " status fixreleased"
+ if "--webbrowser" in options or "-w" in options:
+ import webbrowser
+ webbrowser.open('http://pad.lv/%s>' % (bug.id,))
+
+
+def read_news_bugnos(path):
+ """Read the bug numbers closed by a particular NEWS file
+
+ :param path: Path to the NEWS file
+ :return: list of bug numbers that were closed.
+ """
+ # Pattern to find bug numbers
+ bug_pattern = re.compile("\#([0-9]+)")
+ ret = set()
+ f = open(path, 'r')
+ try:
+ section = ""
+ for l in f.readlines():
+ if l.strip() == "":
+ try:
+ parenthesed = section.rsplit("(", 1)[1]
+ except IndexError:
+ parenthesed = ""
+ # Empty line, next section begins
+ for bugno in [int(m) for m in bug_pattern.findall(parenthesed)]:
+ ret.add((bugno, section))
+ section = ""
+ else:
+ section += l
+ return ret
+ finally:
+ f.close()
+
+
+def print_bug_url(bugno):
+ print '<URL:http://pad.lv/%s>' % (bugno,)
+
+launchpad = hydrazine.create_session()
+bugnos = read_news_bugnos(args[1])
+for bugno, section in bugnos:
+ try:
+ bug = launchpad.bugs[bugno]
+ except errors.HTTPError, e:
+ if e.response.status == 401:
+ print_bug_url(bugno)
+ # Private, we can't access the bug content
+ print '%s is private and cannot be accessed' % (bugno,)
+ continue
+ raise
+
+ found_bzr = False
+ fix_released = False
+ for task in bug.bug_tasks:
+ parts = task.bug_target_name.split('/')
+ if len(parts) == 1:
+ project = parts[0]
+ distribution = None
+ else:
+ project = parts[0]
+ distribution = parts[1]
+ if project == "bzr":
+ found_bzr = True
+ if not fix_released and task.status == "Fix Released":
+ # We could check that the NEWS section and task_status are in
+ # sync, but that would be overkill. (case at hand: bug #416732)
+ fix_released = True
+
+ if not found_bzr:
+ print_bug_url(bugno)
+ print "Bug %d was mentioned in NEWS but is not marked as affecting bzr" % bugno
+ elif not fix_released:
+ print_bug_url(bugno)
+ report_notmarked(bug, task, section)
diff --git a/tools/fixed-in.py b/tools/fixed-in.py
new file mode 100755
index 0000000..98f2529
--- /dev/null
+++ b/tools/fixed-in.py
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+
+# Simple script that will output the release where a given bug was fixed
+# searching the NEWS file
+
+import optparse
+import re
+import sys
+
+
+class NewsParser(object):
+
+ paren_exp_re = re.compile('\(([^)]+)\)')
+ release_re = re.compile("bzr[ -]")
+ release_prefix_length = len('bzr ')
+ bugs_re = re.compile('#([0-9]+)')
+
+ def __init__(self, news):
+ self.news = news
+ # Temporary attributes used by the parser
+ self.release = None
+ self.date = None
+ self.may_be_release = None
+ self.release_markup = None
+ self.entry = ''
+ self.line = None
+ self.lrs = None
+
+ def set_line(self, line):
+ self.line = line
+ self.lrs = line.rstrip()
+
+ def try_release(self):
+ if self.release_re.match(self.lrs) is not None:
+ # May be a new release
+ self.may_be_release = self.lrs
+ # We know the markup will have the same length as the release
+ self.release_markup = '#' * len(self.may_be_release)
+ return True
+ return False
+
+ def confirm_release(self):
+ if self.may_be_release is not None and self.lrs == self.release_markup:
+ # The release is followed by the right markup
+ self.release = self.may_be_release[self.release_prefix_length:]
+ # Wait for the associated date
+ self.date = None
+ return True
+ return False
+
+ def try_date(self):
+ if self.release is None:
+ return False
+ date_re = re.compile(':%s: (NOT RELEASED YET|\d{4}-\d{2}-\d{2})'
+ % (self.release,))
+ match = date_re.match(self.lrs)
+ if match is not None:
+ self.date = match.group(1)
+ return True
+ # The old fashion way
+ released_re = re.compile(':Released:\s+(\d{4}-\d{2}-\d{2})')
+ match = released_re.match(self.lrs)
+ if match is not None:
+ self.date = match.group(1)
+ return True
+ return False
+
+ def add_line_to_entry(self):
+ if self.lrs == '':
+ return False
+ self.entry += self.line
+ return True
+
+ def extract_bugs_from_entry(self):
+ """Possibly extract bugs from a NEWS entry and yield them.
+
+ Not all entries will contain bugs and some entries are even garbage and
+ we don't try to parse them (yet). The trigger is a '#' and what looks
+ like a bug number inside parens to start with. From that we extract
+ authors (when present) and multiple bugs if needed.
+ """
+ # FIXME: Malone entries are different
+ # Join all entry lines to simplify multiple line matching
+ flat_entry = ' '.join(self.entry.splitlines())
+ # Fixed bugs are always inside parens
+ for par in self.paren_exp_re.findall(flat_entry):
+ sharp = par.find('#')
+ if sharp is not None:
+ # We have at least one bug inside parens.
+ bugs = list(self.bugs_re.finditer(par))
+ if bugs:
+ # See where the first bug is mentioned
+ start = bugs[0].start()
+ end = bugs[-1].end()
+ if start == 0:
+ # (bugs/authors)
+ authors = par[end:]
+ else:
+ # (authors/bugs)
+ authors = par[:start]
+ for bug_match in bugs:
+ bug_number = bug_match.group(0)
+ yield (bug_number, authors,
+ self.release, self.date, self.entry)
+ # We've consumed the entry
+ self.entry = ''
+
+ def parse_bugs(self):
+ for line in self.news:
+ self.set_line(line)
+ if self.try_release():
+ continue # line may a be release
+ try:
+ if self.confirm_release():
+ continue # previous line was indeed a release
+ finally:
+ self.may_be_release = None
+ if self.try_date():
+ continue # The release date has been seen
+ if self.add_line_to_entry():
+ continue # accumulate in self.enrty
+ for b in self.extract_bugs_from_entry():
+ yield b # all bugs in the news entry
+
+def main():
+ opt_parser = optparse.OptionParser(
+ usage="""Usage: %prog [options] BUG_NUMBER
+ """)
+ opt_parser.add_option(
+ '-f', '--file', type='str', dest='news_file',
+ help='NEWS file (defaults to ./NEWS)')
+ opt_parser.add_option(
+ '-m', '--message', type='str', dest='msg_re',
+ help='A regexp to search for in the news entry '
+ '(BUG_NUMBER should not be specified in this case)')
+ opt_parser.set_defaults(news_file='./NEWS')
+ (opts, args) = opt_parser.parse_args(sys.argv[1:])
+ if opts.msg_re is not None:
+ if len(args) != 0:
+ opt_parser.error('BUG_NUMBER and -m are mutually exclusive')
+ bug = None
+ msg_re = re.compile(opts.msg_re)
+ elif len(args) != 1:
+ opt_parser.error('Expected a single bug number, got %r' % args)
+ else:
+ bug = args[0]
+
+ news = open(opts.news_file)
+ parser = NewsParser(news)
+ try:
+ seen = 0
+ for b in parser.parse_bugs():
+ (number, authors, release, date, entry,) = b
+ # indent entry
+ entry = '\n'.join([' ' + l for l in entry.splitlines()])
+ found = False
+ if bug is not None:
+ if number[1:] == bug: # Strip the leading '#'
+ found = True
+ elif msg_re.search(entry) is not None:
+ found = True
+ if found:
+ print 'Bug %s was fixed in bzr-%s/%s by %s:' % (
+ number, release, date, authors)
+ print entry
+ seen += 1
+ finally:
+ print '%s bugs seen' % (seen,)
+ news.close()
+
+
+main()
diff --git a/tools/generate_docs.py b/tools/generate_docs.py
new file mode 100755
index 0000000..b27eeba
--- /dev/null
+++ b/tools/generate_docs.py
@@ -0,0 +1,111 @@
+#!/usr/bin/python
+
+# Copyright 2005 Canonical Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""%(prog)s - generate information from built-in bzr help
+
+%(prog)s creates a file with information on bzr in one of
+several different output formats:
+
+ man man page
+ bash_completion bash completion script
+ ...
+
+Examples:
+
+ python2.4 generated-docs.py man
+ python2.4 generated-docs.py bash_completion
+
+Run "%(prog)s --help" for the option reference.
+"""
+import os
+import sys
+from optparse import OptionParser
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
+
+import bzrlib
+from bzrlib import (
+ commands,
+ # Don't remove the following import, it triggers a format registration that
+ # avoid http://pad.lv/956860
+ branch,
+ doc_generate,
+ )
+
+
+def main(argv):
+ parser = OptionParser(usage="""%prog [options] OUTPUT_FORMAT
+
+Available OUTPUT_FORMAT:
+
+ man man page
+ rstx man page in ReStructuredText format
+ bash_completion bash completion script""")
+
+ parser.add_option("-s", "--show-filename",
+ action="store_true", dest="show_filename", default=False,
+ help="print default filename on stdout")
+
+ parser.add_option("-o", "--output", dest="filename", metavar="FILE",
+ help="write output to FILE")
+
+ parser.add_option("-b", "--bzr-name",
+ dest="bzr_name", default="bzr", metavar="EXEC_NAME",
+ help="name of bzr executable")
+
+ parser.add_option("-e", "--examples",
+ action="callback", callback=print_extended_help,
+ help="Examples of ways to call generate_doc")
+
+
+ (options, args) = parser.parse_args(argv)
+
+ if len(args) != 2:
+ parser.print_help()
+ sys.exit(1)
+
+ with bzrlib.initialize():
+ commands.install_bzr_command_hooks()
+ infogen_type = args[1]
+ infogen_mod = doc_generate.get_module(infogen_type)
+ if options.filename:
+ outfilename = options.filename
+ else:
+ outfilename = infogen_mod.get_filename(options)
+ if outfilename == "-":
+ outfile = sys.stdout
+ else:
+ outfile = open(outfilename,"w")
+ if options.show_filename and (outfilename != "-"):
+ sys.stdout.write(outfilename)
+ sys.stdout.write('\n')
+ infogen_mod.infogen(options, outfile)
+
+
+def print_extended_help(option, opt, value, parser):
+ """ Program help examples
+
+ Prints out the examples stored in the docstring.
+
+ """
+ sys.stdout.write(__doc__ % {"prog":sys.argv[0]})
+ sys.stdout.write('\n')
+ sys.exit(0)
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/tools/generate_release_notes.py b/tools/generate_release_notes.py
new file mode 100644
index 0000000..934dd0b
--- /dev/null
+++ b/tools/generate_release_notes.py
@@ -0,0 +1,137 @@
+#!/usr/bin/python
+
+# Copyright 2009-2010 Canonical Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Generate doc/en/release-notes/index.txt from the per-series NEWS files.
+
+NEWS files are kept in doc/en/release-notes/, one file per series, e.g.
+doc/en/release-notes/bzr-2.3.txt
+"""
+
+# XXX: add test_source test that latest doc/en/release-notes/bzr-*.txt has the
+# NEWS file-id (so that merges of new work will tend to always land new NEWS
+# entries in the latest series).
+
+
+import os.path
+import re
+import sys
+from optparse import OptionParser
+
+
+preamble_plain = """\
+####################
+Bazaar Release Notes
+####################
+
+
+.. contents:: List of Releases
+ :depth: 2
+
+"""
+
+preamble_sphinx = """\
+####################
+Bazaar Release Notes
+####################
+
+
+.. toctree::
+ :maxdepth: 2
+
+"""
+
+
+def natural_sort_key(file_name):
+ """Split 'aaa-N.MMbbb' into ('aaa-', N, '.' MM, 'bbb')
+
+ e.g. 1.10b1 will sort as greater than 1.2::
+
+ >>> natural_sort_key('bzr-1.10b1.txt') > natural_sort_key('bzr-1.2.txt')
+ True
+ """
+ file_name = os.path.basename(file_name)
+ parts = re.findall(r'(?:[0-9]+|[^0-9]+)', file_name)
+ result = []
+ for part in parts:
+ if re.match('^[0-9]+$', part) is not None:
+ part = int(part)
+ result.append(part)
+ return tuple(result)
+
+
+def output_news_file_sphinx(out_file, news_file_name):
+ news_file_name = os.path.basename(news_file_name)
+ if not news_file_name.endswith('.txt'):
+ raise AssertionError(
+ 'NEWS file %s does not have .txt extension.'
+ % (news_file_name,))
+ doc_name = news_file_name[:-4]
+ link_text = doc_name.replace('-', ' ')
+ out_file.write(' %s <%s>\n' % (link_text, doc_name))
+
+
+def output_news_file_plain(out_file, news_file_name):
+ f = open(news_file_name, 'rb')
+ try:
+ lines = f.readlines()
+ finally:
+ f.close()
+ title = os.path.basename(news_file_name)[len('bzr-'):-len('.txt')]
+ for line in lines:
+ if line == '####################\n':
+ line = '#' * len(title) + '\n'
+ elif line == 'Bazaar Release Notes\n':
+ line = title + '\n'
+ elif line == '.. toctree::\n':
+ continue
+ elif line == ' :maxdepth: 1\n':
+ continue
+ out_file.write(line)
+ out_file.write('\n\n')
+
+
+def main(argv):
+ # Check usage
+ parser = OptionParser(usage="%prog OUTPUT_FILE NEWS_FILE [NEWS_FILE ...]")
+ (options, args) = parser.parse_args(argv)
+ if len(args) < 2:
+ parser.print_help()
+ sys.exit(1)
+
+ # Open the files and do the work
+ out_file_name = args[0]
+ news_file_names = sorted(args[1:], key=natural_sort_key, reverse=True)
+
+ if os.path.basename(out_file_name) == 'index.txt':
+ preamble = preamble_sphinx
+ output_news_file = output_news_file_sphinx
+ else:
+ preamble = preamble_plain
+ output_news_file = output_news_file_plain
+
+ out_file = open(out_file_name, 'w')
+ try:
+ out_file.write(preamble)
+ for news_file_name in news_file_names:
+ output_news_file(out_file, news_file_name)
+ finally:
+ out_file.close()
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/tools/package_docs.py b/tools/package_docs.py
new file mode 100644
index 0000000..4742f59
--- /dev/null
+++ b/tools/package_docs.py
@@ -0,0 +1,109 @@
+#!/usr/bin/python
+
+# Copyright 2009 Canonical Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import sys
+import tarfile
+from optparse import OptionParser
+from shutil import copy2, copytree, rmtree
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
+
+
+def package_docs(section, src_build, dest_html, dest_downloads):
+ """Package docs from a Sphinx _build directory into target directories.
+
+ :param section: section in the website being built
+ :param src_build: the _build directory
+ :param dest_html: the directory where html should go
+ :param downloads: the directory where downloads should go
+ """
+ # Copy across the HTML. Explicitly delete the html destination
+ # directory first though because copytree insists on it not existing.
+ src_html = os.path.join(src_build, 'html')
+ if os.path.exists(dest_html):
+ rmtree(dest_html)
+ copytree(src_html, dest_html)
+
+ # Package the html as a downloadable archive
+ archive_root = "bzr-%s-html" % (section,)
+ archive_basename = "%s.tar.bz2" % (archive_root,)
+ archive_name = os.path.join(dest_downloads, archive_basename)
+ build_archive(src_html, archive_name, archive_root, 'bz2')
+
+ # Copy across the PDF docs, if any, including the quick ref card
+ pdf_files = []
+ quick_ref = os.path.join(src_html,
+ '_static/%s/bzr-%s-quick-reference.pdf' % (section, section))
+ if os.path.exists(quick_ref):
+ pdf_files.append(quick_ref)
+ src_pdf = os.path.join(src_build, 'latex')
+ if os.path.exists(src_pdf):
+ for name in os.listdir(src_pdf):
+ if name.endswith('.pdf'):
+ pdf_files.append(os.path.join(src_pdf, name))
+ if pdf_files:
+ dest_pdf = os.path.join(dest_downloads, 'pdf-%s' % (section,))
+ if not os.path.exists(dest_pdf):
+ os.mkdir(dest_pdf)
+ for pdf in pdf_files:
+ copy2(pdf, dest_pdf)
+
+ # TODO: copy across the CHM files, if any
+
+
+def build_archive(src_dir, archive_name, archive_root, format):
+ print "creating %s ..." % (archive_name,)
+ tar = tarfile.open(archive_name, "w:%s" % (format,))
+ for relpath in os.listdir(src_dir):
+ src_path = os.path.join(src_dir, relpath)
+ archive_path = os.path.join(archive_root, relpath)
+ tar.add(src_path, arcname=archive_path)
+ tar.close()
+
+
+def main(argv):
+ # Check usage. The first argument is the parent directory of
+ # the Sphinx _build directory. It will typically be 'doc/xx'.
+ # The second argument is the website build directory.
+ parser = OptionParser(usage="%prog SOURCE-DIR WEBSITE-BUILD-DIR")
+ (options, args) = parser.parse_args(argv)
+ if len(args) != 2:
+ parser.print_help()
+ sys.exit(1)
+
+ # Get the section - locale code or 'developers'
+ src_dir = args[0]
+ section = os.path.basename(src_dir)
+ src_build = os.path.join(src_dir, '_build')
+
+ # Create the destination directories if they doesn't exist.
+ dest_dir = args[1]
+ dest_html = os.path.join(dest_dir, section)
+ dest_downloads = os.path.join(dest_dir, 'downloads')
+ for d in [dest_dir, dest_downloads]:
+ if not os.path.exists(d):
+ print "creating directory %s ..." % (d,)
+ os.mkdir(d)
+
+ # Package and copy the files across
+ package_docs(section, src_build, dest_html, dest_downloads)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/tools/package_mf.py b/tools/package_mf.py
new file mode 100644
index 0000000..7e2c2ce
--- /dev/null
+++ b/tools/package_mf.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2008 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Custom module finder for entire package"""
+
+import modulefinder
+import os
+import sys
+
+
+class CustomModuleFinder(modulefinder.ModuleFinder):
+ """Custom module finder for processing python packages,
+ e.g. bzr plugins packages.
+
+ :param path: list of directories to search for modules;
+ if not specified, python standard library only is used.
+ """
+
+ def __init__(self, path=None, debug=0, excludes=[], replace_paths=[]):
+ if path is None:
+ path = [os.path.dirname(os.__file__)] # only python std lib
+ modulefinder.ModuleFinder.__init__(self, path, debug, excludes,
+ replace_paths)
+
+ def run_package(self, package_path):
+ """Recursively process each module in package with run_script method.
+
+ :param package_path: path to package directory.
+ """
+ stack = [package_path]
+ while stack:
+ curdir = stack.pop(0)
+ py = os.listdir(curdir)
+ for i in py:
+ full = os.path.join(curdir, i)
+ if os.path.isdir(full):
+ init = os.path.join(full, '__init__.py')
+ if os.path.isfile(init):
+ stack.append(full)
+ continue
+ if not i.endswith('.py'):
+ continue
+ if i == 'setup.py': # skip
+ continue
+ self.run_script(full)
+
+ def get_result(self):
+ """Return 2-tuple: (list of packages, list of modules)"""
+ keys = self.modules.keys()
+ keys.sort()
+ mods = []
+ packs = []
+ for key in keys:
+ m = self.modules[key]
+ if not m.__file__: # skip builtins
+ continue
+ if m.__path__:
+ packs.append(key)
+ elif key != '__main__':
+ mods.append(key)
+ return (packs, mods)
+
+
+if __name__ == '__main__':
+ package = sys.argv[1]
+
+ mf = CustomModuleFinder()
+ mf.run_package(package)
+
+ packs, mods = mf.get_result()
+
+ print 'Packages:'
+ print packs
+
+ print 'Modules:'
+ print mods
diff --git a/tools/packaging/build-packages.sh b/tools/packaging/build-packages.sh
new file mode 100755
index 0000000..be94ef0
--- /dev/null
+++ b/tools/packaging/build-packages.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# Helper to build packages for the various distributions
+
+if [ -z "$UBUNTU_RELEASES" ]; then
+ echo "Configure the distro platforms that you want to"
+ echo "build with a line like:"
+ echo ' export UBUNTU_RELEASES="dapper feisty gutsy hardy intrepid jaunty"'
+ exit 1
+fi
+
+for DISTRO in $UBUNTU_RELEASES; do
+ (cd "$PACKAGE-$DISTRO" && bzr builddeb -S)
+done
diff --git a/tools/packaging/lp-upload-release b/tools/packaging/lp-upload-release
new file mode 100755
index 0000000..fd61f7a
--- /dev/null
+++ b/tools/packaging/lp-upload-release
@@ -0,0 +1,82 @@
+#! /bin/zsh -e
+
+# upload a release file to Launchpad
+#
+# usage: lp-upload-release [--staging] bzr-1.2.3.tgz
+
+setopt extended_glob
+
+if [ "$1" = "--staging" ]
+then
+ shift
+ server="staging.launchpad.net"
+else
+ server="launchpad.net"
+fi
+
+if [ $# -ne 1 ]
+then
+ echo "usage: lp-upload-release VERSION FILENAME"
+ exit 2
+fi
+
+if [ -z "$EMAIL" ]
+then
+ echo "please set $EMAIL to an address registered with Launchpad"
+ exit 2
+fi
+
+upfile="$1"
+if [ \! -f "$1" ]
+then
+ echo "$upfile is not a file"
+ exit 2
+fi
+
+sigfile="$upfile.sig"
+if [ \! -f "$sigfile" ]
+then
+ echo "couldn't find gpg signature $sigfile"
+ exit 2
+fi
+
+# just in case
+gpg $sigfile
+
+# parse the product and release number out of a filename like
+# "bzr-foo-1.23rc1.tar.gz"
+
+# need to strip off the .tar too for .tar.gz files
+headpart=${${upfile:r}%.tar}
+filetype="${upfile#${headpart}}"
+basename="${${headpart:t}%%-[0-9].*}"
+version="${${headpart:t}##*-}"
+echo $basename $version $filetype
+
+# bzr puts just the first part of the version into the series, e.g. 1.8 from 1.8rc1
+series=${version%(rc|beta|alpha)*}
+
+for v in basename version filetype series
+do
+ echo "$v=${(P)v}"
+done
+
+curl -u "$EMAIL" \
+ https://$server/$basename/$series/$version/+adddownloadfile \
+ -F field.description="$basename $version source" \
+ -F field.filecontent="@${upfile}" \
+ -F field.contenttype=CODETARBALL \
+ -F field.actions.add=Upload
+
+reported_md5=$( curl https://$server/$basename/$series/$version/+download/$filetail/+md5 )
+expected_md5="$(md5sum "$upfile")"
+expected_md5=${${(z)expected_md5}[1]}
+
+for v in reported_md5 expected_md5
+do
+ echo "$v=${(P)v}"
+done
+
+[ $reported_md5 = $expected_md5 ]
+
+# vim: sw=4
diff --git a/tools/packaging/update-changelogs.sh b/tools/packaging/update-changelogs.sh
new file mode 100755
index 0000000..3d1dcbc
--- /dev/null
+++ b/tools/packaging/update-changelogs.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+# Helper to insert a new entry in debian/changelog
+
+if [ -z "$UBUNTU_RELEASES" ]; then
+ echo "Configure the distro platforms that you want to"
+ echo "build with a line like:"
+ echo ' export UBUNTU_RELEASES="dapper feisty gutsy hardy intrepid jaunty"'
+ exit 1
+fi
+
+if [ "x$VERSION" = "x" ]; then
+ echo "Missing version"
+ echo "You want something like:"
+ echo " VERSION=1.6~rc1-1~bazaar1 update-changelogs.sh"
+ echo "or"
+ echo " VERSION=1.6-1~bazaar1 update-changelogs.sh"
+ exit
+fi
+
+if [ -z "$1" ]; then
+ MSG="New upstream release"
+else
+ MSG=$1
+fi
+
+for DISTRO in $UBUNTU_RELEASES; do
+ PPAVERSION="$VERSION~${DISTRO}1"
+ (
+ echo "Updating changelog for $DISTRO"
+ cd "$PACKAGE-$DISTRO" &&
+ dch -v $PPAVERSION -D $DISTRO "$MSG." &&
+ bzr commit -m "$MSG: $PPAVERSION"
+ )
+done
diff --git a/tools/packaging/update-control.sh b/tools/packaging/update-control.sh
new file mode 100755
index 0000000..ae5cd80
--- /dev/null
+++ b/tools/packaging/update-control.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+# Helper to run sed on versions in dependencies in debian/control
+
+if [ -z "$UBUNTU_RELEASES" ]; then
+ echo "Configure the distro platforms that you want to"
+ echo "build with a line like:"
+ echo ' export UBUNTU_RELEASES="dapper feisty gutsy hardy intrepid jaunty"'
+ exit 1
+fi
+
+OLD_VERSION=$1
+NEW_VERSION=$2
+NEXT_VERSION=$3
+if [ -z "$OLD_VERSION" -o -z "$NEW_VERSION" -o -z "$NEXT_VERSION" ]; then
+ echo "Missing version"
+ echo "You want something like:"
+ echo " update-control.sh 1.5 1.6 1.7"
+ exit
+fi
+
+if [ "$PACKAGE" = "bzr" ]; then
+ continue
+fi
+for DISTRO in $UBUNTU_RELEASES; do
+ PPAVERSION="$VERSION~${DISTRO}1"
+ (
+ echo "Updating control for $DISTRO"
+ cd "$PACKAGE-$DISTRO" &&
+ sed -i -e "s/$NEW_VERSION~/$NEXT_VERSION~/;s/$OLD_VERSION~/$NEW_VERSION~/" control &&
+ bzr commit -m "New upstream release: $PPAVERSION, update control"
+ )
+done
diff --git a/tools/packaging/update-packaging-branches.sh b/tools/packaging/update-packaging-branches.sh
new file mode 100755
index 0000000..c56f457
--- /dev/null
+++ b/tools/packaging/update-packaging-branches.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# Helper for updating all of the packaging branches
+
+if [ -z "$UBUNTU_RELEASES" ]; then
+ echo "Configure the distro platforms that you want to"
+ echo "build with a line like:"
+ echo ' export UBUNTU_RELEASES="dapper feisty gutsy hardy intrepid jaunty"'
+ exit 1
+fi
+
+for DISTRO in $UBUNTU_RELEASES; do
+ if [ -d "$PACKAGE-$DISTRO" ] ; then
+ echo "Updating $PACKAGE-$DISTRO"
+ bzr update $PACKAGE-$DISTRO
+ if [ "$PACKAGE" = "bzr-svn" ] ; then
+ cd $PACKAGE-$DISTRO
+ bzr merge http://bzr.debian.org/pkg-bazaar/bzr-svn/unstable/
+ cd ..
+ fi
+ else
+ SRC="lp:~bzr/ubuntu/$DISTRO/$PACKAGE/bzr-ppa"
+ echo "Checking out $SRC"
+ bzr co $SRC $PACKAGE-$DISTRO
+ fi
+done
diff --git a/tools/prepare_for_latex.py b/tools/prepare_for_latex.py
new file mode 100644
index 0000000..970c2df
--- /dev/null
+++ b/tools/prepare_for_latex.py
@@ -0,0 +1,173 @@
+#!/usr/bin/python
+#
+# Modify reStructuredText 'image' directives by adding a percentage 'width'
+# attribute so that the images are scaled to fit on the page when the document
+# is renderd to LaTeX, and add a center alignment.
+#
+# Also convert references to PNG images to use PDF files generated from SVG
+# files if available.
+#
+# Without the explicit size specification, the images are ridiculously huge and
+# most extend far off the right side of the page.
+#
+# Copyright (C) 2009 Colin D Bennett
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import re
+import shutil
+import sys
+from sys import argv
+from subprocess import call
+
+verbose = False
+
+IMAGE_DIRECTIVE_PATTERN = re.compile(ur'^..\s+image::\s+(.*)\s+$')
+DIRECTIVE_ELEMENT_PATTERN = re.compile(ur'^\s+:[^:]+:\s+')
+
+class Converter(object):
+ def __init__(self, srcdir, destdir):
+ self.srcdir = srcdir
+ self.destdir = destdir
+
+ # Process .txt files in sourcedir, generating output in destdir.
+ def process_files(self):
+ for filename in os.listdir(self.srcdir):
+ # Process all text files in the current directory.
+ if filename.endswith('.txt'):
+ inpath = os.path.join(self.srcdir, filename)
+ outpath = os.path.join(self.destdir, filename)
+ self._process_file(inpath, outpath)
+
+ def _process_file(self, inpath, outpath):
+ infile = open(inpath, 'r')
+ outfile = open(outpath, 'w')
+ foundimg = False
+ for line in infile:
+ if foundimg and DIRECTIVE_ELEMENT_PATTERN.match(line) is None:
+ if verbose:
+ print('Fixing image directive')
+ # The preceding image directive has no elements.
+ outfile.write(' :width: 85%\n')
+ outfile.write(' :align: center\n')
+ foundimg = False
+
+ image_fixer = ImageFixer(self.srcdir, self.destdir)
+ image_fixer_lambda = lambda match: image_fixer.substitute_pdf_image(match)
+ line = IMAGE_DIRECTIVE_PATTERN.sub(image_fixer_lambda, line)
+ directive_match = IMAGE_DIRECTIVE_PATTERN.match(line)
+ if directive_match is not None:
+ image_src = directive_match.group(1)
+ if verbose:
+ print('Image ' + image_src + ' in ' + filename
+ + ': ' + line.strip())
+
+ foundimg = True
+ outfile.write(line)
+ outfile.close()
+ infile.close()
+
+class ImageFixer(object):
+ def __init__(self, srcdir, destdir):
+ self.srcdir = srcdir
+ self.destdir = destdir
+
+ def substitute_pdf_image(self, match):
+ prefix = match.string[:match.start(1)]
+ newname = self.convert_image_to_pdf(match.group(1))
+ suffix = match.string[match.end(1):]
+ return prefix + newname + suffix
+
+ def replace_extension(self, path, newext):
+ if path.endswith(newext):
+ raise Exception("File '" + path + "' already has extension '"
+ + newext +"'")
+ dot = path.rfind('.')
+ if dot == -1:
+ return path + newext
+ else:
+ return path[:dot] + newext
+
+ # Possibly use an SVG alternative to a PNG image, converting the SVG image
+ # to a PDF first. Whether or not a conversion is made, the image to use is
+ # written to the destination directory and the path to use in the RST #
+ # source is returned.
+ def convert_image_to_pdf(self, filename):
+ # Make the directory structure for the image in the destination dir.
+ image_dirname = os.path.dirname(filename)
+ if image_dirname:
+ image_dirpath = os.path.join(self.destdir, image_dirname)
+ if not os.path.exists(image_dirpath):
+ os.mkdir(image_dirpath)
+
+ # Decide how to handle this image.
+ if filename.endswith('.png'):
+ # See if there is a vector alternative.
+ svgfile = self.replace_extension(filename, '.svg')
+ svgpath = os.path.join(self.srcdir, svgfile)
+ if os.path.exists(svgpath):
+ if verbose:
+ print('Using SVG alternative to PNG')
+ # Convert SVG to PDF with Inkscape.
+ pdffile = self.replace_extension(filename, '.pdf')
+ pdfpath = os.path.join(self.destdir, pdffile)
+ if call(['/usr/bin/inkscape',
+ '--export-pdf=' + pdfpath, svgpath]) != 0:
+ raise Exception("Conversion to pdf failed")
+ return pdffile
+
+ # No conversion, just copy the file.
+ srcpath = os.path.join(self.srcdir, filename)
+ destpath = os.path.join(self.destdir, filename)
+ shutil.copyfile(srcpath, destpath)
+ return filename
+
+if __name__ == '__main__':
+ IN_DIR_OPT = '--in-dir='
+ OUT_DIR_OPT = '--out-dir='
+ srcdir = None
+ destdir = None
+
+ if len(argv) < 2:
+ print('Usage: ' + argv[0] + ' ' + IN_DIR_OPT + 'INDIR '
+ + OUT_DIR_OPT + 'OUTDIR')
+ print
+ print('This will convert all .txt files in INDIR into file in OUTDIR')
+ print('while adjusting the use of images and possibly converting SVG')
+ print('images to PDF files so LaTeX can include them.')
+ sys.exit(1)
+
+ for arg in argv[1:]:
+ if arg == '-v' or arg == '--verbose':
+ verbose = True
+ elif arg.startswith(IN_DIR_OPT):
+ srcdir = arg[len(IN_DIR_OPT):]
+ elif arg.startswith(OUT_DIR_OPT):
+ destdir = arg[len(OUT_DIR_OPT):]
+ else:
+ print('Invalid argument ' + arg)
+ sys.exit(1)
+
+ if srcdir is None or destdir is None:
+ print('Please specify the ' + IN_DIR_OPT + ' and '
+ + OUT_DIR_OPT + ' options.')
+ sys.exit(1)
+
+ if not os.path.exists(destdir):
+ os.mkdir(destdir)
+ Converter(srcdir, destdir).process_files()
+
+# vim: set ts=4 sw=4 et:
diff --git a/tools/riodemo.py b/tools/riodemo.py
new file mode 100644
index 0000000..3b97a57
--- /dev/null
+++ b/tools/riodemo.py
@@ -0,0 +1,60 @@
+
+
+
+# \subsection{Example usage}
+#
+# \textbf{XXX:} Move these to object serialization code.
+
+def write_revision(writer, revision):
+ s = Stanza(revision=revision.revision_id,
+ committer=revision.committer,
+ timezone=long(revision.timezone),
+ timestamp=long(revision.timestamp),
+ inventory_sha1=revision.inventory_sha1,
+ message=revision.message)
+ for parent_id in revision.parent_ids:
+ s.add('parent', parent_id)
+ for prop_name, prop_value in revision.properties.items():
+ s.add(prop_name, prop_value)
+ writer.write_stanza(s)
+
+def write_inventory(writer, inventory):
+ s = Stanza(inventory_version=7)
+ writer.write_stanza(s)
+
+ for path, ie in inventory.iter_entries():
+ s = Stanza()
+ s.add(ie.kind, ie.file_id)
+ for attr in ['name', 'parent_id', 'revision', \
+ 'text_sha1', 'text_size', 'executable', \
+ 'symlink_target', \
+ ]:
+ attr_val = getattr(ie, attr, None)
+ if attr == 'executable' and attr_val == 0:
+ continue
+ if attr == 'parent_id' and attr_val == 'TREE_ROOT':
+ continue
+ if attr_val is not None:
+ s.add(attr, attr_val)
+ writer.write_stanza(s)
+
+
+def read_inventory(inv_file):
+ """Read inventory object from rio formatted inventory file"""
+ from bzrlib.inventory import Inventory, InventoryFile
+ s = read_stanza(inv_file)
+ assert s['inventory_version'] == 7
+ inv = Inventory()
+ for s in read_stanzas(inv_file):
+ kind, file_id = s.items[0]
+ parent_id = None
+ if 'parent_id' in s:
+ parent_id = s['parent_id']
+ if kind == 'file':
+ ie = InventoryFile(file_id, s['name'], parent_id)
+ ie.text_sha1 = s['text_sha1']
+ ie.text_size = s['text_size']
+ else:
+ raise NotImplementedError()
+ inv.add(ie)
+ return inv
diff --git a/tools/rst2html.py b/tools/rst2html.py
new file mode 100755
index 0000000..788a0be
--- /dev/null
+++ b/tools/rst2html.py
@@ -0,0 +1,55 @@
+#! /usr/bin/env python
+
+# Originally by Dave Goodger, from the docutils, distribution.
+#
+# Modified for Bazaar to accommodate options containing dots
+#
+# This file is in the public domain.
+
+"""
+A minimal front end to the Docutils Publisher, producing HTML.
+"""
+
+try:
+ import locale
+ locale.setlocale(locale.LC_ALL, '')
+except:
+ pass
+
+import docutils
+from docutils.core import publish_cmdline, default_description
+
+if True: # this is still required in the distutils trunk as-at June 2008.
+ from docutils.parsers.rst.states import Body
+ # we have some option names that contain dot; which is not allowed by
+ # python-docutils 0.4-4 -- so monkeypatch in a better pattern
+ #
+ # This is a bit gross to patch because all this is built up at load time.
+ Body.pats['optname'] = r'[a-zA-Z0-9][a-zA-Z0-9._-]*'
+ Body.pats['longopt'] = r'(--|/)%(optname)s([ =]%(optarg)s)?' % Body.pats
+ Body.pats['option'] = r'(%(shortopt)s|%(longopt)s)' % Body.pats
+ Body.patterns['option_marker'] = r'%(option)s(, %(option)s)*( +| ?$)' % Body.pats
+
+
+description = ('Generates (X)HTML documents from standalone reStructuredText '
+ 'sources. ' + default_description)
+
+
+# workaround for bug with <xxx id="tags" name="tags"> in IE
+from docutils.writers import html4css1
+
+class IESafeHtmlTranslator(html4css1.HTMLTranslator):
+
+ def starttag(self, node, tagname, suffix='\n', empty=0, **attributes):
+ x = html4css1.HTMLTranslator.starttag(self, node, tagname, suffix,
+ empty, **attributes)
+ y = x.replace('id="tags"', 'id="tags_"')
+ y = y.replace('name="tags"', 'name="tags_"')
+ y = y.replace('href="#tags"', 'href="#tags_"')
+ return y
+
+mywriter = html4css1.Writer()
+mywriter.translator_class = IESafeHtmlTranslator
+
+
+publish_cmdline(writer=mywriter, description=description)
diff --git a/tools/rst2pdf.py b/tools/rst2pdf.py
new file mode 100755
index 0000000..87bb389
--- /dev/null
+++ b/tools/rst2pdf.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+# $Id: rst2pdf.py 5560 2008-05-20 13:00:31Z milde $
+
+# rst2pdf.py
+# ==========
+# ::
+
+"""
+A front end to the Docutils Publisher, producing PDF.
+
+Produces a latex file with the "latex" writer and converts
+it to PDF with the "rubber" building system for LaTeX documents.
+"""
+
+# ``rst2pdf.py`` is a PDF front-end for docutils that is compatible
+# with the ``rst2*.py`` front ends of the docutils_ suite.
+# It enables the generation of PDF documents from a reStructuredText source in
+# one step.
+#
+# It is implemented as a combination of docutils' ``rst2latex.py``
+# by David Goodger and rubber_ by Emmanuel Beffara.
+#
+# Copyright: © 2008 Günter Milde
+# Licensed under the `Apache License, Version 2.0`_
+# Provided WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND
+#
+# Changelog
+# ---------
+#
+# ===== ========== =======================================================
+# 0.1 2008-05-20 first attempt
+# ===== ========== =======================================================
+#
+# ::
+
+_version = 0.1
+
+
+# Imports
+# =======
+# ::
+
+#from pprint import pprint # for debugging
+import os
+
+# Docutils::
+
+try:
+ import locale
+ locale.setlocale(locale.LC_ALL, '')
+except:
+ pass
+
+from docutils.core import default_usage, default_description, Publisher
+
+# Rubber (rubber is not installed in the PYTHONPATH)::
+
+import sys
+sys.path.append("/usr/share/rubber")
+
+try:
+ import rubber.cmdline
+ import rubber.cmd_pipe
+except ImportError:
+ print "Cannot find the rubber modules, rubber not installed correctly."
+ sys.exit(1)
+
+# Generate the latex file
+# =======================
+#
+# We need to replace the <destination> by a intermediate latex file path.
+# The most reliable way to get the value of <destination> is to
+# call the Publisher "by hand", and query its settings.
+#
+# Modeled on the publish_cmdline() function of docutils.core
+#
+# Default values::
+
+reader=None
+reader_name='standalone'
+parser=None
+parser_name='restructuredtext'
+writer=None
+writer_name='pseudoxml'
+settings=None
+settings_spec=None
+settings_overrides=None
+config_section=None
+enable_exit_status=1
+argv=None
+usage=default_usage
+description=default_description
+
+# Argument values given to publish_cmdline() in rst2latex.py::
+
+description = ('Generates PDF documents from standalone reStructuredText '
+ 'sources using the "latex" Writer and the "rubber" '
+ 'building system for LaTeX documents. ' + default_description)
+writer_name = 'latex'
+
+# Set up the publisher::
+
+pub = Publisher(reader, parser, writer, settings=settings)
+pub.set_components(reader_name, parser_name, writer_name)
+
+# Parse the command line args
+# (Publisher.publish does this in a try statement)::
+
+pub.process_command_line(argv, usage, description, settings_spec,
+ config_section, **(settings_overrides or {}))
+# pprint(pub.settings.__dict__)
+
+# Get source and destination path::
+
+source = pub.settings._source
+destination = pub.settings._destination
+# print source, destination
+
+# Generate names for the temporary files and set ``destination`` to temporary
+# latex file:
+#
+# make_name() from rubber.cmd_pipe checks that no existing file is
+# overwritten. If we are going to support rubbers ``--inplace`` and ``--into``
+# options, the chdir() must occure before this point to have the check in the
+# right directory. ::
+
+tmppath = rubber.cmd_pipe.make_name()
+texpath = tmppath + ".tex"
+pdfpath = tmppath + ".pdf"
+
+pub.settings._destination = texpath
+
+# Now do the rst -> latex conversion::
+
+pub.publish(argv, usage, description, settings_spec, settings_overrides,
+ config_section=config_section, enable_exit_status=enable_exit_status)
+
+
+# Generating the PDF document with rubber
+# =======================================
+#
+#
+# rubber_ has no documentet API for programmatic use. We simualate a command
+# line call and pass command line arguments (see man: rubber-pipe) in an array::
+
+rubber_argv = ["--pdf", # use pdflatex to produce PDF
+ "--short", # Display LaTeX’s error messages one error per line.
+ texpath
+ ]
+
+# Get a TeX processing class instance and do the latex->pdf conversion::
+
+tex_processor = rubber.cmdline.Main()
+tex_processor(rubber_argv)
+
+# Rename output to _destination or print to stdout::
+
+if destination is None:
+ pdffile = file(pdfpath)
+ print pdffile.read()
+ pdffile.close()
+else:
+ os.rename(pdfpath, destination)
+
+# Clean up (remove intermediate files)
+#
+# ::
+
+tex_processor(["--clean"] + rubber_argv)
+os.remove(texpath)
+
+
+# .. References
+#
+# .. _docutils: http://docutils.sourceforge.net/
+# .. _rubber: http://www.pps.jussieu.fr/~beffara/soft/rubber/
+# .. _Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+#
diff --git a/tools/rst2prettyhtml.py b/tools/rst2prettyhtml.py
new file mode 100755
index 0000000..9b02b09
--- /dev/null
+++ b/tools/rst2prettyhtml.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+import errno
+import os
+from StringIO import StringIO
+import sys
+
+try:
+ from docutils.core import publish_file
+ from docutils.parsers import rst
+except ImportError:
+ print "Missing dependency. Please install docutils."
+ sys.exit(1)
+try:
+ from elementtree.ElementTree import XML
+ from elementtree import HTMLTreeBuilder
+except ImportError:
+ print "Missing dependency. Please install ElementTree."
+ sys.exit(1)
+try:
+ import kid
+except ImportError:
+ print "Missing dependency. Please install Kid."
+ sys.exit(1)
+
+
+def kidified_rest(rest_file, template_name):
+ xhtml_file = StringIO()
+ # prevent docutils from autoclosing the StringIO
+ xhtml_file.close = lambda: None
+ xhtml = publish_file(rest_file, writer_name='html', destination=xhtml_file,
+ settings_overrides={"doctitle_xform": 0}
+
+ )
+ xhtml_file.seek(0)
+ xml = HTMLTreeBuilder.parse(xhtml_file)
+ head = xml.find('head')
+ body = xml.find('body')
+ assert head is not None
+ assert body is not None
+ template=kid.Template(file=template_name,
+ head=head, body=body)
+ return (template.serialize(output="html"))
+
+
+def safe_open(filename, mode):
+ try:
+ return open(filename, mode + 'b')
+ except IOError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ sys.stderr.write('file not found: %s\n' % sys.argv[2])
+ sys.exit(3)
+
+
+def main(template, source=None, target=None):
+ if source is not None:
+ rest_file = safe_open(source, 'r')
+ else:
+ rest_file = sys.stdin
+ if target is not None:
+ out_file = safe_open(target, 'w')
+ else:
+ out_file = sys.stdout
+ out_file.write(kidified_rest(rest_file, template))
+
+assert len(sys.argv) > 1
+
+# Strip options so only the arguments are passed
+args = [x for x in sys.argv[1:] if not x.startswith('-')]
+main(*args)
diff --git a/tools/subunit-sum b/tools/subunit-sum
new file mode 100755
index 0000000..2b042b1
--- /dev/null
+++ b/tools/subunit-sum
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# Copyright (C) 2011 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Displays the sum of a counter found in a subunit stream.
+
+Each test have (or not) the counter as a named detail in the stream.
+"""
+
+import subunit
+import sys
+import testtools
+import unittest
+
+
+class TestSumCounter(testtools.TestResult):
+
+ def __init__(self, counter_names):
+ """Create a FilterResult object outputting to stream."""
+ testtools.TestResult.__init__(self)
+ self.counter_names = counter_names
+ self.totals = {}
+ self.longest = 0
+ for name in counter_names:
+ l = len(name)
+ if l > self.longest: self.longest = l
+ self.totals[name] = 0
+
+ def addSuccess(self, test, details):
+ for name in self.counter_names:
+ try:
+ counter_text = ''.join(details[name].iter_text())
+ except KeyError, e:
+ # this counter doesn't exist for the test
+ continue
+ self.totals[name] += int(counter_text)
+
+ def display_totals(self, stream):
+ for name in self.counter_names:
+ stream.write('%-*s: %s\n'
+ % (self.longest, name, self.totals[name]))
+
+
+counter_names = sys.argv[1:]
+result = TestSumCounter(counter_names)
+test = subunit.ProtocolTestCase(sys.stdin)
+test.run(result)
+result.display_totals(sys.stdout)
diff --git a/tools/time_graph.py b/tools/time_graph.py
new file mode 100644
index 0000000..bfe6056
--- /dev/null
+++ b/tools/time_graph.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+import random
+import os
+import time
+import sys
+import optparse
+from bzrlib import (
+ branch,
+ commands,
+ graph,
+ ui,
+ trace,
+ _known_graph_py,
+ _known_graph_pyx,
+ )
+from bzrlib.ui import text
+
+p = optparse.OptionParser()
+p.add_option('--quick', default=False, action='store_true')
+p.add_option('--max-combinations', default=500, type=int)
+p.add_option('--lsprof', default=None, type=str)
+opts, args = p.parse_args(sys.argv[1:])
+
+trace.enable_default_logging()
+ui.ui_factory = text.TextUIFactory()
+
+begin = time.clock()
+if len(args) >= 1:
+ b = branch.Branch.open(args[0])
+else:
+ b = branch.Branch.open('.')
+b.lock_read()
+try:
+ g = b.repository.get_graph()
+ parent_map = dict(p for p in g.iter_ancestry([b.last_revision()])
+ if p[1] is not None)
+finally:
+ b.unlock()
+end = time.clock()
+
+print 'Found %d nodes, loaded in %.3fs' % (len(parent_map), end - begin)
+
+def all_heads_comp(g, combinations):
+ h = []
+ pb = ui.ui_factory.nested_progress_bar()
+ try:
+ for idx, combo in enumerate(combinations):
+ if idx & 0x1f == 0:
+ pb.update('proc', idx, len(combinations))
+ h.append(g.heads(combo))
+ finally:
+ pb.finished()
+ return h
+
+combinations = []
+# parents = parent_map.keys()
+# for p1 in parents:
+# for p2 in random.sample(parents, 10):
+# combinations.append((p1, p2))
+# Times for random sampling of 10x1150 of bzrtools
+# Graph KnownGraph
+# 96.1s vs 25.7s :)
+# Times for 500 'merge parents' from bzr.dev
+# 25.6s vs 45.0s :(
+
+for revision_id, parent_ids in parent_map.iteritems():
+ if parent_ids is not None and len(parent_ids) > 1:
+ combinations.append(parent_ids)
+# The largest portion of the graph that has to be walked for a heads() check
+# combinations = [('john@arbash-meinel.com-20090312021943-tu6tcog48aiujx4s',
+# 'john@arbash-meinel.com-20090312130552-09xa2xsitf6rilzc')]
+if opts.max_combinations > 0 and len(combinations) > opts.max_combinations:
+ combinations = random.sample(combinations, opts.max_combinations)
+
+print ' %d combinations' % (len(combinations),)
+
+def combi_graph(graph_klass, comb):
+ # DEBUG
+ graph._counters[1] = 0
+ graph._counters[2] = 0
+
+ begin = time.clock()
+ g = graph_klass(parent_map)
+ if opts.lsprof is not None:
+ heads = commands.apply_lsprofiled(opts.lsprof, all_heads_comp, g, comb)
+ else:
+ heads = all_heads_comp(g, comb)
+ end = time.clock()
+ return dict(elapsed=(end - begin), graph=g, heads=heads)
+
+def report(name, g):
+ print '%s: %.3fs' % (name, g['elapsed'])
+ counters_used = False
+ for c in graph._counters:
+ if c:
+ counters_used = True
+ if counters_used:
+ print ' %s' % (graph._counters,)
+
+known_python = combi_graph(_known_graph_py.KnownGraph, combinations)
+report('Known', known_python)
+
+known_pyrex = combi_graph(_known_graph_pyx.KnownGraph, combinations)
+report('Known (pyx)', known_pyrex)
+
+def _simple_graph(parent_map):
+ return graph.Graph(graph.DictParentsProvider(parent_map))
+
+if opts.quick:
+ if known_python['heads'] != known_pyrex['heads']:
+ import pdb; pdb.set_trace()
+ print 'ratio: %.1f:1 faster' % (
+ known_python['elapsed'] / known_pyrex['elapsed'],)
+else:
+ orig = combi_graph(_simple_graph, combinations)
+ report('Orig', orig)
+
+ if orig['heads'] != known_pyrex['heads']:
+ import pdb; pdb.set_trace()
+
+ print 'ratio: %.1f:1 faster' % (
+ orig['elapsed'] / known_pyrex['elapsed'],)
diff --git a/tools/weavemerge.sh b/tools/weavemerge.sh
new file mode 100644
index 0000000..27c9889
--- /dev/null
+++ b/tools/weavemerge.sh
@@ -0,0 +1,57 @@
+#! /bin/zsh -xe
+
+weave init test.weave
+
+weave add test.weave <<EOF
+aaa
+bbb
+ccc
+EOF
+
+weave add test.weave 0 <<EOF
+aaa
+bbb
+stuff from martin
+ccc
+ddd
+EOF
+
+weave add test.weave 0 <<EOF
+aaa
+bbb
+stuff from john
+more john stuff
+ccc
+EOF
+
+weave add test.weave 1 2 <<EOF
+aaa
+bbb
+stuff from martin
+fix up merge
+more john stuff
+ccc
+ddd
+EOF
+
+weave add test.weave 3 <<EOF
+aaa
+bbb
+stuff from martin
+fix up merge
+modify john's code
+ccc
+ddd
+add stuff here
+EOF
+
+# v5
+weave add test.weave 2 <<EOF
+aaa
+bbb
+stuff from john
+more john stuff
+john replaced ccc line
+EOF
+
+# now try merging 5(2) with 4(3(2 1)) \ No newline at end of file
diff --git a/tools/win32/__init__.py b/tools/win32/__init__.py
new file mode 100644
index 0000000..b4e98a6
--- /dev/null
+++ b/tools/win32/__init__.py
@@ -0,0 +1 @@
+# modules and utilities for making win32 installer
diff --git a/tools/win32/bazaar.url b/tools/win32/bazaar.url
new file mode 100644
index 0000000..f3eee0f
--- /dev/null
+++ b/tools/win32/bazaar.url
@@ -0,0 +1,7 @@
+[InternetShortcut]
+URL=http://bazaar.canonical.com
+Hotkey=0
+IconIndex=0
+IconFile=
+WorkingDirectory=
+ShowCommand=1
diff --git a/tools/win32/bootstrap.py b/tools/win32/bootstrap.py
new file mode 100644
index 0000000..2ad3e57
--- /dev/null
+++ b/tools/win32/bootstrap.py
@@ -0,0 +1,77 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 90478 2008-08-27 22:44:46Z georgyberdyshev $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+is_jython = sys.platform.startswith('java')
+
+try:
+ import pkg_resources
+except ImportError:
+ ez = {}
+ exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+ ).read() in ez
+ ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+ import pkg_resources
+
+if sys.platform == 'win32':
+ def quote(c):
+ if ' ' in c:
+ return '"%s"' % c # work around spawn lamosity on windows
+ else:
+ return c
+else:
+ def quote (c):
+ return c
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+ws = pkg_resources.working_set
+
+if is_jython:
+ import subprocess
+
+ assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd',
+ quote(tmpeggs), 'zc.buildout'],
+ env=dict(os.environ,
+ PYTHONPATH=
+ ws.find(pkg_resources.Requirement.parse('setuptools')).location
+ ),
+ ).wait() == 0
+
+else:
+ assert os.spawnle(
+ os.P_WAIT, sys.executable, quote (sys.executable),
+ '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout',
+ dict(os.environ,
+ PYTHONPATH=
+ ws.find(pkg_resources.Requirement.parse('setuptools')).location
+ ),
+ ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)
diff --git a/tools/win32/build_release.py b/tools/win32/build_release.py
new file mode 100644
index 0000000..b66fa8f
--- /dev/null
+++ b/tools/win32/build_release.py
@@ -0,0 +1,206 @@
+#!/cygdrive/C/Python25/python
+"""A script to help automate the build process."""
+
+# When preparing a new release, make sure to set all of these to the latest
+# values.
+VERSIONS = {
+ 'bzr': '1.17',
+ 'qbzr': '0.12',
+ 'bzrtools': '1.17.0',
+ 'bzr-svn': '0.6.3',
+ 'bzr-rewrite': '0.5.2',
+ 'subvertpy': '0.6.8',
+}
+
+# This will be passed to 'make' to ensure we build with the right python
+PYTHON='/cygdrive/c/Python25/python'
+
+# Create the final build in this directory
+TARGET_ROOT='release'
+
+DEBUG_SUBPROCESS = True
+
+
+import os
+import shutil
+import subprocess
+import sys
+
+
+BZR_EXE = None
+def bzr():
+ global BZR_EXE
+ if BZR_EXE is not None:
+ return BZR_EXE
+ try:
+ subprocess.call(['bzr', '--version'], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ BZR_EXE = 'bzr'
+ except OSError:
+ try:
+ subprocess.call(['bzr.bat', '--version'], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ BZR_EXE = 'bzr.bat'
+ except OSError:
+ raise RuntimeError('Could not find bzr or bzr.bat on your path.')
+ return BZR_EXE
+
+
+def call_or_fail(*args, **kwargs):
+ """Call a subprocess, and fail if the return code is not 0."""
+ if DEBUG_SUBPROCESS:
+ print ' calling: "%s"' % (' '.join(args[0]),)
+ p = subprocess.Popen(*args, **kwargs)
+ (out, err) = p.communicate()
+ if p.returncode != 0:
+ raise RuntimeError('Failed to run: %s, %s' % (args, kwargs))
+ return out
+
+
+TARGET = None
+def get_target():
+ global TARGET
+ if TARGET is not None:
+ return TARGET
+ out = call_or_fail([sys.executable, get_bzr_dir() + '/bzr',
+ 'version', '--short'], stdout=subprocess.PIPE)
+ version = out.strip()
+ TARGET = os.path.abspath(TARGET_ROOT + '-' + version)
+ return TARGET
+
+
+def clean_target():
+ """Nuke the target directory so we know we are starting from scratch."""
+ target = get_target()
+ if os.path.isdir(target):
+ print "Deleting: %s" % (target,)
+ shutil.rmtree(target)
+
+def get_bzr_dir():
+ return 'bzr.' + VERSIONS['bzr']
+
+
+def update_bzr():
+ """Make sure we have the latest bzr in play."""
+ bzr_dir = get_bzr_dir()
+ if not os.path.isdir(bzr_dir):
+ bzr_version = VERSIONS['bzr']
+ bzr_url = 'lp:bzr/' + bzr_version
+ print "Getting bzr release %s from %s" % (bzr_version, bzr_url)
+ call_or_fail([bzr(), 'co', bzr_url, bzr_dir])
+ else:
+ print "Ensuring %s is up-to-date" % (bzr_dir,)
+ call_or_fail([bzr(), 'update', bzr_dir])
+
+
+def create_target():
+ target = get_target()
+ print "Creating target dir: %s" % (target,)
+ call_or_fail([bzr(), 'co', get_bzr_dir(), target])
+
+
+def get_plugin_trunk_dir(plugin_name):
+ return '%s/trunk' % (plugin_name,)
+
+
+def get_plugin_release_dir(plugin_name):
+ return '%s/%s' % (plugin_name, VERSIONS[plugin_name])
+
+
+def get_plugin_trunk_branch(plugin_name):
+ return 'lp:%s' % (plugin_name,)
+
+
+def update_plugin_trunk(plugin_name):
+ trunk_dir = get_plugin_trunk_dir(plugin_name)
+ if not os.path.isdir(trunk_dir):
+ plugin_trunk = get_plugin_trunk_branch(plugin_name)
+ print "Getting latest %s trunk" % (plugin_name,)
+ call_or_fail([bzr(), 'co', plugin_trunk,
+ trunk_dir])
+ else:
+ print "Ensuring %s is up-to-date" % (trunk_dir,)
+ call_or_fail([bzr(), 'update', trunk_dir])
+ return trunk_dir
+
+
+def _plugin_tag_name(plugin_name):
+ if plugin_name in ('bzr-svn', 'bzr-rewrite', 'subvertpy'):
+ return '%s-%s' % (plugin_name, VERSIONS[plugin_name])
+ # bzrtools and qbzr use 'release-X.Y.Z'
+ return 'release-' + VERSIONS[plugin_name]
+
+
+def update_plugin(plugin_name):
+ release_dir = get_plugin_release_dir(plugin_name)
+ if not os.path.isdir(plugin_name):
+ if plugin_name in ('bzr-svn', 'bzr-rewrite'):
+ # bzr-svn uses a different repo format
+ call_or_fail([bzr(), 'init-repo', '--rich-root-pack', plugin_name])
+ else:
+ os.mkdir(plugin_name)
+ if os.path.isdir(release_dir):
+ print "Removing existing dir: %s" % (release_dir,)
+ shutil.rmtree(release_dir)
+ # First update trunk
+ trunk_dir = update_plugin_trunk(plugin_name)
+ # Now create the tagged directory
+ tag_name = _plugin_tag_name(plugin_name)
+ print "Creating the branch %s" % (release_dir,)
+ call_or_fail([bzr(), 'co', '-rtag:%s' % (tag_name,),
+ trunk_dir, release_dir])
+ return release_dir
+
+
+def install_plugin(plugin_name):
+ release_dir = update_plugin(plugin_name)
+ # at least bzrtools doesn't like you to call 'setup.py' unless you are in
+ # that directory specifically, so we cd, rather than calling it from
+ # outside
+ print "Installing %s" % (release_dir,)
+ call_or_fail([sys.executable, 'setup.py', 'install', '-O1',
+ '--install-lib=%s' % (get_target(),)],
+ cwd=release_dir)
+
+
+def update_tbzr():
+ tbzr_loc = os.environ.get('TBZR', None)
+ if tbzr_loc is None:
+ raise ValueError('You must set TBZR to the location of tortoisebzr.')
+ print 'Updating %s' % (tbzr_loc,)
+ call_or_fail([bzr(), 'update', tbzr_loc])
+
+
+def build_installer():
+ target = get_target()
+ print
+ print
+ print '*' * 60
+ print 'Building standalone installer'
+ call_or_fail(['make', 'PYTHON=%s' % (PYTHON,), 'installer'],
+ cwd=target)
+
+
+def main(args):
+ import optparse
+
+ p = optparse.OptionParser(usage='%prog [OPTIONS]')
+ opts, args = p.parse_args(args)
+
+ update_bzr()
+ update_tbzr()
+ clean_target()
+ create_target()
+ install_plugin('subvertpy')
+ install_plugin('bzrtools')
+ install_plugin('qbzr')
+ install_plugin('bzr-svn')
+ install_plugin('bzr-rewrite')
+
+ build_installer()
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
+
+# vim: ts=4 sw=4 sts=4 et ai
diff --git a/tools/win32/buildout-templates/bin/build-installer.bat.in b/tools/win32/buildout-templates/bin/build-installer.bat.in
new file mode 100644
index 0000000..bc65773
--- /dev/null
+++ b/tools/win32/buildout-templates/bin/build-installer.bat.in
@@ -0,0 +1,108 @@
+@echo off
+setlocal
+
+set ROOT=${buildout:directory}
+
+set RELEASE=%ROOT%\release\bzr.${settings:bzr-release}
+set DEV=%ROOT%\dev\bzr.dev
+set TARGET=
+set BZR_TARGET=
+set PLUGIN_TARGET=
+
+set SVN_DEV=${buildout:directory}/${svn-dev:destination}
+set SVN_BDB=${buildout:directory}/${svn-db4:destination}
+set SVN_LIBINTL=${buildout:directory}/${svn-libintl:destination}
+set TBZR=${buildout:directory}/tbzr/trunk
+set INSTALLERS=%ROOT%\installers
+set PYTHON=${buildout:executable}
+
+set ORIGINAL_PYTHONPATH=%PYTHONPATH%
+set ORIGINAL_DIRECTORY=%CD%
+
+set BUILD_ERROR=0
+
+set TORTOISE_OVERLAYS_MSI_WIN32_CMD=%PYTHON% %ROOT%/ostools.py basename ${settings:tortoise-overlays-win32-url}
+FOR /f "tokens=1 delims= " %%G IN ('%TORTOISE_OVERLAYS_MSI_WIN32_CMD%') DO set BASENAME=%%G
+set TORTOISE_OVERLAYS_MSI_WIN32=${buildout:directory}/tortoise-overlays/%BASENAME%
+
+set TORTOISE_OVERLAYS_MSI_X64_CMD=%PYTHON% %ROOT%/ostools.py basename ${settings:tortoise-overlays-x64-url}
+FOR /f "tokens=1 delims= " %%G IN ('%TORTOISE_OVERLAYS_MSI_X64_CMD%') DO set BASENAME=%%G
+set TORTOISE_OVERLAYS_MSI_X64=${buildout:directory}/tortoise-overlays/%BASENAME%
+
+FOR /f "tokens=1 delims= " %%G IN ('cygpath %PYTHON%') DO set CYG_PYTHON=%%G
+
+:ARGS
+if "%1"=="release" (set TARGET=%RELEASE%) & (set BZR_TARGET=release) & shift & goto ARGS
+if "%1"=="dev" (set TARGET=%DEV%) & (set BZR_TARGET=trunk) & shift & goto ARGS
+if "%1"=="plugin-release" (set PLUGIN_TARGET=release) & shift & goto ARGS
+if "%1"=="plugin-dev" (set PLUGIN_TARGET=trunk) & shift & goto ARGS
+
+if not defined TARGET (set TARGET=%RELEASE%) & (set BZR_TARGET=release)
+if not defined PLUGIN_TARGET (set PLUGIN_TARGET=release)
+
+%PYTHON% %ROOT%/ostools.py remove %TARGET%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+%PYTHON% %ROOT%/ostools.py makedir %TARGET%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+%PYTHON% %ROOT%/ostools.py makedir %INSTALLERS%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+rem Use %COMSPEC% /c in case bzr is actually a .bat file
+%COMSPEC% /c bzr co %ROOT%/bzr/%BZR_TARGET% %TARGET%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+rem Build the python installers first, because we don't want to
+rem include any of the 3rd-party plugins, because we don't bundle
+rem their dependencies.
+cd %TARGET%
+
+rem This is slightly redundant with 'make installer-all'
+rem except in that case we have to do cygwin path translations for all the
+rem different versions of python
+${settings:python24} setup.py bdist_wininst --install-script="bzr-win32-bdist-postinstall.py" -d .
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+${settings:python25} setup.py bdist_wininst --install-script="bzr-win32-bdist-postinstall.py" -d .
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+${settings:python26} setup.py bdist_wininst --install-script="bzr-win32-bdist-postinstall.py" -d .
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+cd %ROOT%/subvertpy/%PLUGIN_TARGET%
+%PYTHON% setup.py install -O1 --install-lib=%TARGET%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+cd %ROOT%/bzrtools/%PLUGIN_TARGET%
+%PYTHON% setup.py install -O1 --install-lib=%TARGET%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+cd %ROOT%/qbzr/%PLUGIN_TARGET%
+%PYTHON% setup.py install -O1 --install-lib=%TARGET%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+cd %ROOT%/bzr-svn/%PLUGIN_TARGET%
+%PYTHON% setup.py install -O1 --install-lib=%TARGET%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+cd %ROOT%/bzr-rewrite/%PLUGIN_TARGET%
+%PYTHON% setup.py install -O1 --install-lib=%TARGET%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+set PYTHONPATH=%PYTHONPATH%;%TARGET%
+cd %ROOT%/tbzr/trunk
+%PYTHON% setup.py build
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+cd %TARGET%
+make installer PYTHON=%CYG_PYTHON%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+%PYTHON% %ROOT%/ostools.py copytodir %TARGET%/bzr*.exe %INSTALLERS%
+@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End
+
+:End
+set PYTHONPATH=%ORIGINAL_PYTHONPATH%
+cd %ORIGINAL_DIRECTORY%
+exit /b %BUILD_ERROR%
diff --git a/tools/win32/buildout.cfg b/tools/win32/buildout.cfg
new file mode 100644
index 0000000..bc899b7
--- /dev/null
+++ b/tools/win32/buildout.cfg
@@ -0,0 +1,197 @@
+[buildout]
+newest = false
+versions = versions
+parts =
+ svn-lib
+ svn-dev
+ svn-db4
+ svn-libintl
+ zlib
+ tortoise-overlays-win32
+ tortoise-overlays-x64
+
+ bzr
+ qbzr
+ tbzr
+ bzrtools
+ bzr-svn
+ bzr-rewrite
+ subvertpy
+ templates
+
+develop =
+
+[versions]
+zc.buildout = 1.2.1
+setuptools = 0.6c9
+z3c.recipe.filetemplate = 2.0.3
+gf.recipe.bzr = 1.0rc8
+
+[settings]
+python24=c:\Python24\python.exe
+python25=c:\Python25\python.exe
+python26=c:\Python26\python.exe
+download-ignore-existing = false
+svn-dev-url =
+ http://subversion.tigris.org/files/documents/15/45228/svn-win32-1.5.6_dev.zip
+svn-lib-url =
+ http://subversion.tigris.org/files/documents/15/45222/svn-win32-1.5.6.zip
+svn-db4-url =
+ http://subversion.tigris.org/files/documents/15/32472/db-4.4.20-win32.zip
+svn-libintl-url =
+ http://subversion.tigris.org/files/documents/15/20739/svn-win32-libintl.zip
+tortoise-overlays-win32-url = http://guest:password@tortoisesvn.tigris.org/svn/tortoisesvn/TortoiseOverlays/version-1.0.4/bin/TortoiseOverlays-1.0.4.11886-win32.msi
+tortoise-overlays-x64-url = http://guest:password@tortoisesvn.tigris.org/svn/tortoisesvn/TortoiseOverlays/version-1.0.4/bin/TortoiseOverlays-1.0.4.11886-x64.msi
+zlib-url =
+ http://www.zlib.net/zlib123-dll.zip
+
+bzr-release = 1.18
+# Older releases were @ http://bazaar-vcs.org, new releases are hosted directly
+# on Launchpad
+# bzr-release-url = http://bazaar-vcs.org/bzr/bzr.${settings:bzr-release}
+bzr-release-url = lp:bzr/${settings:bzr-release}
+bzr-trunk-url = lp:bzr
+
+bzrtools-release = 1.18.0
+bzrtools-release-tag = tag:release-${settings:bzrtools-release}
+bzrtools-trunk-url = lp:bzrtools
+
+qbzr-release = 0.13.1
+qbzr-release-tag = tag:release-${settings:qbzr-release}
+qbzr-trunk-url = lp:qbzr
+
+tbzr-release = 0.1.0
+tbzr-release-tag = tag:release-${settings:tbzr-release}
+tbzr-trunk-url = lp:tortoisebzr
+
+bzr-svn-release = 0.6.4
+bzr-svn-release-tag = tag:bzr-svn-${settings:bzr-svn-release}
+bzr-svn-trunk-url = lp:bzr-svn
+
+# This isn't a typo, as of last release, the branch is lp:bzr-rewrite but the
+# tag is bzr-rebase-0.5.3
+bzr-rewrite-release = 0.5.3
+bzr-rewrite-release-tag = tag:bzr-rebase-${settings:bzr-rewrite-release}
+bzr-rewrite-trunk-url = lp:bzr-rewrite
+
+subvertpy-release = 0.6.8
+subvertpy-release-tag = tag:subvertpy-${settings:subvertpy-release}
+subvertpy-trunk-url = lp:subvertpy
+
+[templates]
+recipe = z3c.recipe.filetemplate
+source-directory = buildout-templates
+
+[svn-lib]
+recipe = hexagonit.recipe.download
+url = ${settings:svn-lib-url}
+ignore-existing = ${settings:download-ignore-existing}
+strip-top-level-dir = true
+destination = svn
+
+# This package needs to be extracted on top of svn-lib above, so we
+# explicitly set it to ignore existing here.
+[svn-dev]
+recipe = hexagonit.recipe.download
+url = ${settings:svn-dev-url}
+ignore-existing = true
+strip-top-level-dir = true
+destination = svn
+
+[svn-db4]
+recipe = hexagonit.recipe.download
+url = ${settings:svn-db4-url}
+ignore-existing = ${settings:download-ignore-existing}
+strip-top-level-dir = true
+destination = db4
+
+[svn-libintl]
+recipe = hexagonit.recipe.download
+url = ${settings:svn-libintl-url}
+ignore-existing = ${settings:download-ignore-existing}
+strip-top-level-dir = true
+destination = libintl
+
+[zlib]
+recipe = hexagonit.recipe.download
+url = ${settings:zlib-url}
+ignore-existing = ${settings:download-ignore-existing}
+strip-top-level-dir = false
+destination = zlib
+
+[tortoise-overlays-win32]
+recipe = hexagonit.recipe.download
+url = ${settings:tortoise-overlays-win32-url}
+ignore-existing = ${settings:download-ignore-existing}
+destination = tortoise-overlays
+download-only = true
+
+[tortoise-overlays-x64]
+recipe = hexagonit.recipe.download
+url = ${settings:tortoise-overlays-x64-url}
+ignore-existing = ${settings:download-ignore-existing}
+destination = tortoise-overlays
+download-only = true
+
+[bzr]
+recipe = gf.recipe.bzr:strict
+shared-repo = false
+format = 1.9
+urls =
+ ${settings:bzr-release-url} release
+ ${settings:bzr-trunk-url} trunk
+develop = false
+
+[bzrtools]
+recipe = gf.recipe.bzr:strict
+shared-repo = true
+format = 2a
+urls =
+ ${settings:bzrtools-trunk-url} trunk
+ ${buildout:directory}/bzrtools/trunk@${settings:bzrtools-release-tag} release
+develop = false
+
+[qbzr]
+recipe = gf.recipe.bzr:strict
+shared-repo = true
+format = 1.9
+urls =
+ ${settings:qbzr-trunk-url} trunk
+ ${buildout:directory}/qbzr/trunk@${settings:qbzr-release-tag} release
+develop = false
+
+[tbzr]
+recipe = gf.recipe.bzr:strict
+shared-repo = true
+format = 1.9
+urls =
+ ${settings:tbzr-trunk-url} trunk
+ ${buildout:directory}/tbzr/trunk@${settings:tbzr-release-tag} release
+develop = false
+
+[bzr-svn]
+recipe = gf.recipe.bzr:strict
+shared-repo = true
+format = 2a
+urls =
+ ${settings:bzr-svn-trunk-url} trunk
+ ${buildout:directory}/bzr-svn/trunk@${settings:bzr-svn-release-tag} release
+develop = false
+
+[bzr-rewrite]
+recipe = gf.recipe.bzr:strict
+shared-repo = true
+format = 1.9-rich-root
+urls =
+ ${settings:bzr-rewrite-trunk-url} trunk
+ ${buildout:directory}/bzr-rewrite/trunk@${settings:bzr-rewrite-release-tag} release
+develop = false
+
+[subvertpy]
+recipe = gf.recipe.bzr:strict
+shared-repo = true
+format = 1.9-rich-root
+urls =
+ ${settings:subvertpy-trunk-url} trunk
+ ${buildout:directory}/subvertpy/trunk@${settings:subvertpy-release-tag} release
+develop = false
diff --git a/tools/win32/bzr-win32-bdist-postinstall.py b/tools/win32/bzr-win32-bdist-postinstall.py
new file mode 100644
index 0000000..a5d1de5
--- /dev/null
+++ b/tools/win32/bzr-win32-bdist-postinstall.py
@@ -0,0 +1,145 @@
+# (c) Canonical Ltd, 2006
+# written by Alexander Belchenko for bzr project
+#
+# This script will be executed after installation of bzrlib package
+# and before installer exits.
+# All printed data will appear on the last screen of installation
+# procedure.
+# The main goal of this script is to create special batch file
+# launcher for bzr. Typical content of this batch file is:
+# @python bzr %*
+#
+# This file works only on Windows 2000/XP. For win98 there is
+# should be "%1 %2 %3 %4 %5 %6 %7 %8 %9" instead of "%*".
+# Or even more complex thing.
+#
+# [bialix]: bzr de-facto does not support win98.
+# Although it seems to work on. Sometimes.
+# 2006/07/30 added minimal support of win98.
+# 2007/01/30 added *real* support of win98.
+
+import os
+import sys
+import _winreg
+
+from bzrlib import win32utils
+
+
+def _quoted_path(path):
+ if ' ' in path:
+ return '"' + path + '"'
+ else:
+ return path
+
+def _win_batch_args():
+ if win32utils.winver == 'Windows NT':
+ return '%*'
+ else:
+ return '%1 %2 %3 %4 %5 %6 %7 %8 %9'
+
+
+if "-install" in sys.argv[1:]:
+ # try to detect version number automatically
+ try:
+ import bzrlib
+ except ImportError:
+ ver = ''
+ else:
+ ver = bzrlib.__version__
+
+ ##
+ # XXX change message for something more appropriate
+ print """Bazaar %s
+
+Congratulation! Bzr successfully installed.
+
+""" % ver
+
+ batch_path = "bzr.bat"
+ prefix = sys.exec_prefix
+ try:
+ ##
+ # try to create
+ scripts_dir = os.path.join(prefix, "Scripts")
+ script_path = _quoted_path(os.path.join(scripts_dir, "bzr"))
+ python_path = _quoted_path(os.path.join(prefix, "python.exe"))
+ args = _win_batch_args()
+ batch_str = "@%s %s %s" % (python_path, script_path, args)
+ # support of win98
+ # if there is no HOME for bzr then set it for Bazaar manually
+ base = os.environ.get('BZR_HOME', None)
+ if base is None:
+ base = win32utils.get_appdata_location()
+ if base is None:
+ base = os.environ.get('HOME', None)
+ if base is None:
+ base = os.path.splitdrive(sys.prefix)[0] + '\\'
+ batch_str = ("@SET BZR_HOME=" + _quoted_path(base) + "\n" +
+ batch_str)
+
+ batch_path = os.path.join(scripts_dir, "bzr.bat")
+ f = file(batch_path, "w")
+ f.write(batch_str)
+ f.close()
+ file_created(batch_path) # registering manually created files for
+ # auto-deinstallation procedure
+ ##
+ # inform user where batch launcher is.
+ print "Created:", batch_path
+ print "Use this batch file to run bzr"
+ except Exception, e:
+ print "ERROR: Unable to create %s: %s" % (batch_path, e)
+
+ ## this hunk borrowed from pywin32_postinstall.py
+ # use bdist_wininst builtins to create a shortcut.
+ # CSIDL_COMMON_PROGRAMS only available works on NT/2000/XP, and
+ # will fail there if the user has no admin rights.
+ if get_root_hkey()==_winreg.HKEY_LOCAL_MACHINE:
+ try:
+ fldr = get_special_folder_path("CSIDL_COMMON_PROGRAMS")
+ except OSError:
+ # No CSIDL_COMMON_PROGRAMS on this platform
+ fldr = get_special_folder_path("CSIDL_PROGRAMS")
+ else:
+ # non-admin install - always goes in this user's start menu.
+ fldr = get_special_folder_path("CSIDL_PROGRAMS")
+
+ # make Bazaar entry
+ fldr = os.path.join(fldr, 'Bazaar')
+ if not os.path.isdir(fldr):
+ os.mkdir(fldr)
+ directory_created(fldr)
+
+ # link to documentation
+ docs = os.path.join(sys.exec_prefix, 'Doc', 'Bazaar', 'index.html')
+ dst = os.path.join(fldr, 'Documentation.lnk')
+ create_shortcut(docs, 'Bazaar Documentation', dst)
+ file_created(dst)
+ print 'Documentation for Bazaar: Start => Programs => Bazaar'
+
+ # bzr in cmd shell
+ if os.name == 'nt':
+ cmd = os.environ.get('COMSPEC', 'cmd.exe')
+ args = "/K bzr help"
+ else:
+ # minimal support of win98
+ cmd = os.environ.get('COMSPEC', 'command.com')
+ args = "bzr help"
+ dst = os.path.join(fldr, 'Start bzr.lnk')
+ create_shortcut(cmd,
+ 'Start bzr in cmd shell',
+ dst,
+ args,
+ os.path.join(sys.exec_prefix, 'Scripts'))
+ file_created(dst)
+
+ # uninstall shortcut
+ uninst = os.path.join(sys.exec_prefix, 'Removebzr.exe')
+ dst = os.path.join(fldr, 'Uninstall Bazaar.lnk')
+ create_shortcut(uninst,
+ 'Uninstall Bazaar',
+ dst,
+ '-u bzr-wininst.log',
+ sys.exec_prefix,
+ )
+ file_created(dst)
diff --git a/tools/win32/bzr.iss.cog b/tools/win32/bzr.iss.cog
new file mode 100644
index 0000000..1b745f1
--- /dev/null
+++ b/tools/win32/bzr.iss.cog
@@ -0,0 +1,335 @@
+; Script for Inno Setup installer
+; [[[cog cog.outl('; This script created by Cog from bzr.iss.cog source') ]]]
+; [[[end]]]
+; Cog is http://www.nedbatchelder.com/code/cog/
+
+[Setup]
+AppName=Bazaar
+
+; [[[cog
+; # Python 2.5 compatibility code
+; import os
+; import sys
+; cwd = os.getcwd()
+; if cwd not in sys.path:
+; sys.path.insert(0, cwd)
+; #/Python 2.5 compatibility code
+;
+; import bzrlib
+; try:
+; VERSION = bzrlib.__version__
+; AppVerName = 'Bazaar %s' % VERSION
+; OutputBaseFilename = 'bzr-%s-setup' % VERSION
+; except:
+; VERSION = ''
+; AppVerName = 'Bazaar'
+; OutputBaseFilename = 'bzr-setup'
+;
+; cog.outl('AppVerName=%s' % AppVerName)
+; cog.outl('OutputBaseFilename=%s' % OutputBaseFilename)
+; ]]]
+AppVerName=Bazaar
+OutputBaseFilename=bzr-setup
+; [[[end]]]
+
+DefaultDirName={pf}\Bazaar
+DefaultGroupName=Bazaar
+
+SolidCompression=yes
+
+OutputDir="..\"
+SourceDir="..\..\win32_bzr.exe"
+
+SetupIconFile="..\bzr.ico"
+InfoBeforeFile="..\tools\win32\info.txt"
+
+VersionInfoCompany="Canonical Ltd."
+VersionInfoCopyright="Canonical Ltd., 2005-2008"
+VersionInfoDescription="Windows installer for Bazaar"
+; [[[cog
+; import bzrlib
+; version_number = []
+; for i in bzrlib.version_info[:3]:
+; try:
+; i = int(i)
+; except ValueError:
+; i = 0
+; version_number.append(i)
+; # incremental build number
+; from tools.win32.file_version import *
+; try:
+; version_prev = get_file_version(OutputBaseFilename + '.exe')
+; except (FileNotFound, VersionNotAvailable):
+; pass
+; else:
+; if version_number == list(version_prev[:3]):
+; version_number.append((version_prev[-1]+1) % 65536)
+; version_str = '.'.join(str(i) for i in version_number)
+; cog.outl('VersionInfoVersion="%s"' % version_str)
+; ]]]
+; [[[end]]]
+
+AppComments="Bazaar: Friendly distributed version control system"
+AppPublisher="Canonical Ltd."
+AppPublisherURL="http://bazaar.canonical.com"
+AppSupportURL="http://wiki.bazaar.canonical.com/BzrSupport"
+AppUpdatesURL="http://wiki.bazaar.canonical.com/WindowsDownloads"
+; [[[cog cog.outl('AppVersion=%s' % VERSION) ]]]
+; [[[end]]]
+
+ChangesEnvironment=yes
+; MARKH: PrivilegesRequired=none means it can't be installed by a non-admin
+; user - but sadly we still need admin - eg, tortoise overlays, installing
+; into "\Program Files", installing COM objects etc all must be done by an
+; admin.
+PrivilegesRequired=admin
+
+[Files]
+; Tortoise files - these are at the top as we use 'ExtractTemporaryFile' on
+; the TortoiseOverlays MSI, and inno documents such files should be at the
+; start for best performance.
+; [[[cog
+; if "TBZR" in os.environ: # we need a more formal way of controlling this...
+; tovmsi32 = os.environ["TORTOISE_OVERLAYS_MSI_WIN32"] # point at the 32bit TortoiseOverlays .msi
+; tovmsi64 = os.environ["TORTOISE_OVERLAYS_MSI_X64"] # point at the 64bit TortoiseOverlays .msi
+; cog.outl('Source: "%s"; Flags: dontcopy ignoreversion ; Components: tortoise' % tovmsi32)
+; cog.outl('Source: "%s"; Flags: dontcopy ignoreversion ; Components: tortoise' % tovmsi64)
+; cog.outl('Source: "tbzrcache.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise')
+; cog.outl('Source: "tbzrcachew.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise')
+; cog.outl('Source: "tbzrcommand.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise')
+; cog.outl('Source: "tbzrcommandw.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise')
+; cog.outl('Source: "tbzrtrace.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: debug')
+; # Note 'regserver' here appears to run regsvr32 without elevation, which
+; # is no good for us - so we have a [run] entry below.
+; cog.outl('Source: "tbzr_old.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise')
+; cog.outl('Source: "tbzrshellext_x86.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise')
+; cog.outl('Source: "tbzrshellext_x64.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise; Check: IsWin64;')
+; cog.outl(r'Source: "plugins\qbzr\*"; DestDir: "{app}\plugins\qbzr"; Flags: createallsubdirs ignoreversion recursesubdirs restartreplace uninsrestartdelete; Components: tortoise')
+;
+; cog.outl('Source: "%s\\doc\\*.html"; DestDir: "{app}\\doc\\tbzr"; Flags: ignoreversion; Components: tortoise' % os.environ['TBZR'])
+; ]]]
+; [[[end]]]
+
+; We can't say '*.*' due to optional components.
+Source: "plugins\*.*"; DestDir: "{app}\\plugins"; Flags: createallsubdirs ignoreversion recursesubdirs restartreplace uninsrestartdelete; Components: plugins
+Source: "*.bat"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete;
+Source: "*.url"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete;
+Source: "msvc*.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete;
+Source: "bz*.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete;
+Source: "Python*.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete;
+Source: "lib\*.*"; DestDir: "{app}\lib"; Flags: createallsubdirs ignoreversion recursesubdirs restartreplace uninsrestartdelete;
+Source: "doc\*.*"; DestDir: "{app}\doc"; Flags: createallsubdirs ignoreversion recursesubdirs restartreplace uninsrestartdelete;
+; [[[cog
+; try:
+; import pycurl
+; except ImportError:
+; ca_path = None
+; else:
+; supported = pycurl.version_info()[8]
+; if 'https' in supported:
+; from bzrlib.transport.http.ca_bundle import get_ca_path
+; ca_path = get_ca_path()
+; if ca_path:
+; cog.outl('Source: "%s"; DestDir: "{app}"; Components: cabundle' % ca_path)
+; else:
+; cog.msg('You have pycurl with SSL support, '
+; 'but CA Bundle (curl-ca-bundle.crt) not found!')
+; ]]]
+; [[[end]]]
+
+; imageformats plugins for PyQt4
+; [[[cog
+; plug_dir = os.path.join(os.path.dirname(cog.inFile), # $(bzr_src_root)/tools/win32
+; '..', '..', 'win32_bzr.exe', 'imageformats')
+; if os.path.isdir(plug_dir):
+; cog.outl('Source: "imageformats\\*.*"; DestDir: "{app}\\imageformats"; '
+; 'Flags: createallsubdirs ignoreversion recursesubdirs restartreplace uninsrestartdelete;')
+; else:
+; cog.msg('imageformats plugins for PyQt4 not found')
+; ]]]
+; [[[end]]]
+
+[Types]
+Name: "typical"; Description: "A typical installation"
+Name: "full"; Description: "Full Installation (typical installation plus test utilities)"
+Name: "compact"; Description: "Compact installation"
+Name: "custom"; Description: "Custom installation"; Flags: iscustom
+
+[Components]
+Name: "main"; Description: "Main Files"; Types: full typical compact custom; Flags: fixed
+Name: "plugins"; Description: "Default plugins"; Types: full typical custom;
+; [[[cog
+; if ca_path:
+; cog.outl('Name: "cabundle"; '
+; 'Description: "CA certificates for SSL support"; '
+; 'Types: full typical custom')
+; if "TBZR" in os.environ: # we need a more formal way of controlling this...
+; cog.outl('Name: "tortoise"; Description: "Windows Shell Extensions (TortoiseBZR)"; Types: full typical custom;')
+; cog.outl('Name: "debug"; Description: "Test, diagnostic and debugging utilities"; Types: full custom;')
+;
+; ]]]
+; [[[end]]]
+
+[Dirs]
+Name: "{userappdata}\bazaar\2.0"
+Name: "{app}\plugins"; Flags: uninsalwaysuninstall
+
+
+[Icons]
+Name: "{group}\Documentation index"; Filename: "{app}\doc\index.html"; WorkingDir: "{app}\doc";
+Name: "{group}\Bazaar Home Page"; Filename: "{app}\bazaar.url"; Comment: "http://bazaar.canonical.com";
+Name: "{group}\Start Bzr in cmd shell"; Filename: "{cmd}"; Parameters: "/K start_bzr.bat"; WorkingDir: "{app}"; IconFilename: "{app}\bzr.exe"; Comment: "Open new Bzr session";
+; NOTE: Intent is to change the log file location - the line below will need to change to reflect that.
+Name: "{group}\Open Bzr log file"; Filename: "notepad.exe"; Parameters: "{userdocs}\.bzr.log"; Comment: "Launch notepad to view the bzr log file";
+
+; [[[cog
+; if "TBZR" in os.environ:
+; cog.outl(r'Name: "{group}\TortoiseBZR documentation"; Filename: "{app}\doc\tbzr\index.html"; Comment: "Launch TortoiseBZR documentation";')
+; ]]]
+; [[[end]]]
+; No Uninstall here - Control Panel will do
+
+
+[Tasks]
+Name: Path; Description: "Add {app} directory to PATH environment variable"
+; [[[cog
+; if "TBZR" in os.environ:
+; cog.outl('Name: TBZRReadme; Description: "View the TortoiseBZR Readme"; Components: tortoise')
+; ]]]
+; [[[end]]]
+
+
+[Registry]
+Root: HKLM; Subkey: "SOFTWARE\Bazaar"; Flags: noerror uninsdeletekey
+Root: HKLM; Subkey: "SOFTWARE\Bazaar"; ValueName: "InstallPath"; ValueType: string; ValueData: "{app}"; Flags: noerror
+; Don't write stuff that can be implied
+;Root: HKLM; Subkey: "SOFTWARE\Bazaar"; ValueName: "BzrlibPath"; ValueType: string; ValueData: "{app}\lib\library.zip\bzrlib"; Flags: noerror
+;Root: HKLM; Subkey: "SOFTWARE\Bazaar"; ValueName: "PluginsPath"; ValueType: string; ValueData: "{app}\plugins"; Flags: noerror
+;Root: HKLM; Subkey: "SOFTWARE\Bazaar"; ValueName: "PythonPath"; ValueType: string; ValueData: "{app}\lib\library.zip"; Flags: noerror
+; [[[cog cog.outl('Root: HKLM; Subkey: "SOFTWARE\Bazaar"; ValueName: "Version"; ValueType: string; ValueData: "%s"; Flags: noerror' % VERSION) ]]]
+; [[[end]]]
+
+
+[Run]
+Filename: "{app}\bzr_postinstall.exe"; Parameters: "--start-bzr"; Flags: skipifdoesntexist runhidden;
+Filename: "{app}\bzr_postinstall.exe"; Parameters: "--add-path"; Tasks: Path; Flags: skipifdoesntexist skipifsilent runhidden;
+; [[[cog
+; if "TBZR" in os.environ:
+; cog.outl('Filename: "regsvr32.exe"; Parameters: "/s /i: /n tbzrshellext_x86.dll"; WorkingDir: "{app}"; Components: tortoise; Description: "Registering Tortoise"; StatusMsg: "Registering Tortoise"')
+; cog.outl('Filename: "regsvr32.exe"; Parameters: "/s /i: /n tbzrshellext_x64.dll"; WorkingDir: "{app}"; Components: tortoise; Description: "Registering Tortoise"; StatusMsg: "Registering Tortoise"; Check: IsWin64')
+; cog.outl(r'Filename: "{app}\doc\tbzr\index.html"; Tasks: TBZRReadme; Flags: shellexec')
+; ]]]
+; [[[end]]]
+
+
+[UninstallRun]
+Filename: "{app}\bzr_postinstall.exe"; Parameters: "--delete-path --delete-shell-menu --silent"; Flags: skipifdoesntexist runhidden;
+; [[[cog
+; if "TBZR" in os.environ:
+; cog.outl('Filename: "regsvr32.exe"; Parameters: "/u /s /i: tbzrshellext_x86.dll"; WorkingDir: "{app}"; Components: tortoise; StatusMsg: "Unregistering Tortoise"; Flags: skipifdoesntexist')
+; cog.outl('Filename: "regsvr32.exe"; Parameters: "/u /s /i: tbzrshellext_x64.dll"; WorkingDir: "{app}"; Components: tortoise; StatusMsg: "Unregistering Tortoise"; Flags: skipifdoesntexist; Check: IsWin64')
+; ]]]
+; [[[end]]]
+
+
+[Code]
+const
+ SHCNF_IDLIST = $0000;
+ SHCNE_ASSOCCHANGED = $08000000;
+ WM_QUIT = 18;
+ MOVEFILE_DELAY_UNTIL_REBOOT = 4;
+
+procedure SHChangeNotify(wEventId, uFlags, dwItem1, dwItem2: Integer);
+ external 'SHChangeNotify@shell32.dll stdcall';
+
+function MoveFileEx(lpExistingFileName, lpNewFileName: String; dwFlags: Cardinal): Integer;
+ external 'MoveFileExA@kernel32.dll stdcall';
+
+procedure DeleteFileNowOrLater(filename: string);
+var
+ rc : Integer;
+begin
+ if FileExists(filename) and not DeleteFile(filename) then
+ // can't work out to pass NULL to the API, but an empty string
+ // seems to work OK.
+ MoveFileEx(filename, '', MOVEFILE_DELAY_UNTIL_REBOOT);
+end;
+
+procedure ShutdownTBZR;
+var
+ hwnd: HWND;
+begin
+// [[[cog
+// if "TBZR" not in os.environ:
+// cog.outl(' Exit; // No TSVN set - exit this procedure.')
+// ]]]
+// [[[end]]]
+ // ask the cache process to shut-down.
+ hwnd := FindWindowByClassName('TBZRCache_Taskbar');
+ if hwnd <> 0 then
+ PostMessage(hwnd, WM_QUIT, 1, 0);
+end;
+
+procedure CurStepChanged(CurStep: TSetupStep);
+var
+ S, tovmsi, fqtovmsi, params: String;
+ ErrorCode: Integer;
+begin
+ if CurStep=ssInstall then begin
+ ShutdownTBZR;
+ // In case the user hasn't uninstalled the old version before
+ // upgrading, we unregister and delete some obsolete files
+ // (regsvr32 remains silent even if the file doesn't exist)
+ Exec('regsvr32.exe', '/s /u "' + ExpandConstant('{app}\tbzr.dll') + '"',
+ '', SW_HIDE, ewWaitUntilTerminated, ErrorCode);
+ DeleteFileNowOrLater(ExpandConstant('{app}\tbzr.dll'));
+ DeleteFileNowOrLater(ExpandConstant('{app}\tbzrtest.exe'));
+ DeleteFileNowOrLater(ExpandConstant('{app}\tbzr_tracer.exe'));
+ end;
+
+ if CurStep=ssPostInstall then begin
+ // a couple of post-install tasks
+ if IsComponentSelected('tortoise') then begin
+ // Need to execute:
+ // msiexec /i TortoiseOverlays-1.X.X.XXXX-win32.msi /qn /norestart
+// 64bit notes:
+// We are still primarily a 32bit application - the only 64bit binary is the
+// shell extension, but even then, we need to install the 32bit version too.
+// Thus, we keep tortoise in 32bit "install mode" - meaning we are installed
+// to "\Program Files (x86)". We don't bother trying to install our single
+// 64bit DLL into "\Program Files" - we use a different DLL name for 32 and
+// 64 bit versions, so nothing will conflict.
+// Note however that on a 64bit OS, we only need the 64bit TortoiseOverlays -
+// the 32bit apps using shell extensions still work fine with that.
+// [[[cog
+// if "TBZR" in os.environ:
+// import os
+// cog.outl("if IsWin64 then")
+// cog.outl(" tovmsi := '%s'" % os.path.basename(os.environ["TORTOISE_OVERLAYS_MSI_X64"]))
+// cog.outl("else")
+// cog.outl(" tovmsi := '%s'" % os.path.basename(os.environ["TORTOISE_OVERLAYS_MSI_WIN32"]))
+// else:
+// cog.outl("tovmsi := '';")
+// ]]]
+// [[[end]]]
+ ExtractTemporaryFile(tovmsi);
+ fqtovmsi := AddBackslash(ExpandConstant('{tmp}')) + tovmsi;
+ params := '/i "' + fqtovmsi + '" /qn /norestart';
+ if not ShellExec('', 'msiexec.exe', params, '', SW_HIDE,
+ ewWaitUntilTerminated, ErrorCode) then
+ MsgBox('Failed to install TortoiseOverlays: ' + SysErrorMessage(ErrorCode),
+ mbInformation, MB_OK);
+ // Ideally we could be bzr_postinstall.exe this way too, but
+ // its needed at uninstall time.
+ end;
+ // cause explorer to re-fetch handlers.
+ SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0);
+ end;
+end;
+
+
+function InitializeUninstall(): Boolean;
+begin
+ ShutdownTBZR;
+ result := True;
+end;
diff --git a/tools/win32/bzr_postinstall.py b/tools/win32/bzr_postinstall.py
new file mode 100644
index 0000000..399232f
--- /dev/null
+++ b/tools/win32/bzr_postinstall.py
@@ -0,0 +1,356 @@
+# Copyright (C) 2006, 2007, 2009, 2010 by Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""bzr postinstall helper for win32 installation
+Written by Alexander Belchenko
+
+Dependency: ctypes
+"""
+
+import os
+import shutil
+import sys
+
+
+##
+# CONSTANTS
+
+VERSION = "1.5.20070131"
+
+USAGE = """Bzr postinstall helper for win32 installation
+Usage: %s [options]
+
+OPTIONS:
+ -h, --help - help message
+ -v, --version - version info
+
+ -n, --dry-run - print actions rather than execute them
+ -q, --silent - no messages for user
+
+ --start-bzr - update start_bzr.bat
+ --add-path - add bzr directory to environment PATH
+ --delete-path - delete bzr directory to environment PATH
+ --add-shell-menu - add shell context menu to start bzr session
+ --delete-shell-menu - delete context menu from shell
+ --check-mfc71 - check if MFC71.DLL present in system
+""" % os.path.basename(sys.argv[0])
+
+# Windows version
+_major,_minor,_build,_platform,_text = sys.getwindowsversion()
+# from MSDN:
+# dwPlatformId
+# The operating system platform.
+# This member can be one of the following values.
+# ========================== ======================================
+# Value Meaning
+# -------------------------- --------------------------------------
+# VER_PLATFORM_WIN32_NT The operating system is Windows Vista,
+# 2 Windows Server "Longhorn",
+# Windows Server 2003, Windows XP,
+# Windows 2000, or Windows NT.
+#
+# VER_PLATFORM_WIN32_WINDOWS The operating system is Windows Me,
+# 1 Windows 98, or Windows 95.
+# ========================== ======================================
+if _platform == 2:
+ winver = 'Windows NT'
+else:
+ # don't care about real Windows name, just to force safe operations
+ winver = 'Windows 98'
+
+
+##
+# INTERNAL VARIABLES
+
+(OK, ERROR) = range(2)
+VERSION_FORMAT = "%-50s%s"
+
+
+def main():
+ import ctypes
+ import getopt
+ import re
+ import _winreg
+
+ import locale
+ user_encoding = locale.getpreferredencoding() or 'ascii'
+
+ import ctypes
+
+ hkey_str = {_winreg.HKEY_LOCAL_MACHINE: 'HKEY_LOCAL_MACHINE',
+ _winreg.HKEY_CURRENT_USER: 'HKEY_CURRENT_USER',
+ _winreg.HKEY_CLASSES_ROOT: 'HKEY_CLASSES_ROOT',
+ }
+
+ dry_run = False
+ silent = False
+ start_bzr = False
+ add_path = False
+ delete_path = False
+ add_shell_menu = False
+ delete_shell_menu = False
+ check_mfc71 = False
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "hvnq",
+ ["help", "version",
+ "dry-run",
+ "silent",
+ "start-bzr",
+ "add-path",
+ "delete-path",
+ "add-shell-menu",
+ "delete-shell-menu",
+ "check-mfc71",
+ ])
+
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ print USAGE
+ return OK
+ elif o in ("-v", "--version"):
+ print VERSION_FORMAT % (USAGE.splitlines()[0], VERSION)
+ return OK
+
+ elif o in ('-n', "--dry-run"):
+ dry_run = True
+ elif o in ('-q', '--silent'):
+ silent = True
+
+ elif o == "--start-bzr":
+ start_bzr = True
+ elif o == "--add-path":
+ add_path = True
+ elif o == "--delete-path":
+ delete_path = True
+ elif o == "--add-shell-menu":
+ add_shell_menu = True
+ elif o == "--delete-shell-menu":
+ delete_shell_menu = True
+ elif o == "--check-mfc71":
+ check_mfc71 = True
+
+ except getopt.GetoptError, msg:
+ print str(msg)
+ print USAGE
+ return ERROR
+
+ # message box from Win32API
+ MessageBoxA = ctypes.windll.user32.MessageBoxA
+ MB_OK = 0
+ MB_ICONERROR = 16
+ MB_ICONEXCLAMATION = 48
+
+ bzr_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
+
+ if start_bzr:
+ fname = os.path.join(bzr_dir, "start_bzr.bat")
+ if os.path.isfile(fname):
+ f = file(fname, "r")
+ content = f.readlines()
+ f.close()
+ else:
+ content = ["bzr.exe help\n"]
+
+ for ix in xrange(len(content)):
+ s = content[ix]
+ if re.match(r'.*(?<!\\)bzr\.exe([ "].*)?$',
+ s.rstrip('\r\n'),
+ re.IGNORECASE):
+ content[ix] = s.replace('bzr.exe',
+ '"%s"' % os.path.join(bzr_dir,
+ 'bzr.exe'))
+ elif s.find(r'C:\Program Files\Bazaar') != -1:
+ content[ix] = s.replace(r'C:\Program Files\Bazaar',
+ bzr_dir)
+
+ if dry_run:
+ print "*** Write file: start_bzr.bat"
+ print "*** File content:"
+ print ''.join(content)
+ else:
+ f = file(fname, 'w')
+ f.write(''.join(content))
+ f.close()
+
+ if (add_path or delete_path) and winver == 'Windows NT':
+ # find appropriate registry key:
+ # 1. HKLM\System\CurrentControlSet\Control\SessionManager\Environment
+ # 2. HKCU\Environment
+ keys = ((_winreg.HKEY_LOCAL_MACHINE, (r'System\CurrentControlSet\Control'
+ r'\Session Manager\Environment')),
+ (_winreg.HKEY_CURRENT_USER, r'Environment'),
+ )
+
+ hkey = None
+ for key, subkey in keys:
+ try:
+ hkey = _winreg.OpenKey(key, subkey, 0, _winreg.KEY_ALL_ACCESS)
+ try:
+ path_u, type_ = _winreg.QueryValueEx(hkey, 'Path')
+ except WindowsError:
+ if key != _winreg.HKEY_CURRENT_USER:
+ _winreg.CloseKey(hkey)
+ hkey = None
+ continue
+ else:
+ path_u = u''
+ type_ = _winreg.REG_SZ
+ except EnvironmentError:
+ continue
+ break
+
+ if hkey is None:
+ print "Cannot find appropriate registry key for PATH"
+ else:
+ path_list = [i for i in path_u.split(os.pathsep) if i != '']
+ f_change = False
+ for ix, item in enumerate(path_list[:]):
+ if item == bzr_dir:
+ if delete_path:
+ del path_list[ix]
+ f_change = True
+ elif add_path:
+ print "*** Bzr already in PATH"
+ break
+ else:
+ if add_path and not delete_path:
+ path_list.append(bzr_dir.decode(user_encoding))
+ f_change = True
+
+ if f_change:
+ path_u = os.pathsep.join(path_list)
+ if dry_run:
+ print "*** Registry key %s\\%s" % (hkey_str[key], subkey)
+ print "*** Modify PATH variable. New value:"
+ print path_u
+ else:
+ _winreg.SetValueEx(hkey, 'Path', 0, type_, path_u)
+ _winreg.FlushKey(hkey)
+
+ if not hkey is None:
+ _winreg.CloseKey(hkey)
+
+ if (add_path or delete_path) and winver == 'Windows 98':
+ # mutating autoexec.bat
+ # adding or delete string:
+ # SET PATH=%PATH%;C:\PROGRA~1\Bazaar
+ abat = 'C:\\autoexec.bat'
+ abak = 'C:\\autoexec.bak'
+
+ def backup_autoexec_bat(name, backupname, dry_run):
+ # backup autoexec.bat
+ if os.path.isfile(name):
+ if not dry_run:
+ shutil.copyfile(name, backupname)
+ else:
+ print '*** backup copy of autoexec.bat created'
+
+ GetShortPathName = ctypes.windll.kernel32.GetShortPathNameA
+ buf = ctypes.create_string_buffer(260)
+ if GetShortPathName(bzr_dir, buf, 260):
+ bzr_dir_8_3 = buf.value
+ else:
+ bzr_dir_8_3 = bzr_dir
+ pattern = 'SET PATH=%PATH%;' + bzr_dir_8_3
+
+ # search pattern
+ f = file(abat, 'r')
+ lines = f.readlines()
+ f.close()
+ found = False
+ for i in lines:
+ if i.rstrip('\r\n') == pattern:
+ found = True
+ break
+
+ if delete_path and found:
+ backup_autoexec_bat(abat, abak, dry_run)
+ if not dry_run:
+ f = file(abat, 'w')
+ for i in lines:
+ if i.rstrip('\r\n') != pattern:
+ f.write(i)
+ f.close()
+ else:
+ print '*** Remove line <%s> from autoexec.bat' % pattern
+
+ elif add_path and not found:
+ backup_autoexec_bat(abat, abak, dry_run)
+ if not dry_run:
+ f = file(abat, 'a')
+ f.write(pattern)
+ f.write('\n')
+ f.close()
+ else:
+ print '*** Add line <%s> to autoexec.bat' % pattern
+
+ if add_shell_menu and not delete_shell_menu:
+ hkey = None
+ try:
+ hkey = _winreg.CreateKey(_winreg.HKEY_CLASSES_ROOT,
+ r'Folder\shell\bzr')
+ except EnvironmentError:
+ if not silent:
+ MessageBoxA(None,
+ 'Unable to create registry key for context menu',
+ 'EnvironmentError',
+ MB_OK | MB_ICONERROR)
+
+ if not hkey is None:
+ _winreg.SetValue(hkey, '', _winreg.REG_SZ, 'Bzr Here')
+ hkey2 = _winreg.CreateKey(hkey, 'command')
+ _winreg.SetValue(hkey2, '', _winreg.REG_SZ,
+ '%s /K "%s"' % (
+ os.environ.get('COMSPEC', '%COMSPEC%'),
+ os.path.join(bzr_dir, 'start_bzr.bat')))
+ _winreg.CloseKey(hkey2)
+ _winreg.CloseKey(hkey)
+
+ if delete_shell_menu:
+ try:
+ _winreg.DeleteKey(_winreg.HKEY_CLASSES_ROOT,
+ r'Folder\shell\bzr\command')
+ except EnvironmentError:
+ pass
+
+ try:
+ _winreg.DeleteKey(_winreg.HKEY_CLASSES_ROOT,
+ r'Folder\shell\bzr')
+ except EnvironmentError:
+ pass
+
+ if check_mfc71:
+ try:
+ ctypes.windll.LoadLibrary('mfc71.dll')
+ except WindowsError:
+ MessageBoxA(None,
+ ("Library MFC71.DLL is not found on your system.\n"
+ "This library needed for SFTP transport.\n"
+ "If you need to work via SFTP you should download\n"
+ "this library manually and put it to directory\n"
+ "where Bzr installed.\n"
+ "For detailed instructions see:\n"
+ "http://wiki.bazaar.canonical.com/BzrOnPureWindows"
+ ),
+ "Warning",
+ MB_OK | MB_ICONEXCLAMATION)
+
+ return OK
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/tools/win32/file_version.py b/tools/win32/file_version.py
new file mode 100644
index 0000000..91cc32d
--- /dev/null
+++ b/tools/win32/file_version.py
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+
+"""Get file version.
+Written by Alexander Belchenko, 2006
+"""
+
+import os
+
+import pywintypes # from pywin32 (http://pywin32.sf.net)
+import win32api # from pywin32 (http://pywin32.sf.net)
+
+
+__all__ = ['get_file_version', 'FileNotFound', 'VersionNotAvailable']
+__docformat__ = "restructuredtext"
+
+
+class FileNotFound(Exception):
+ pass
+
+class VersionNotAvailable(Exception):
+ pass
+
+
+def get_file_version(filename):
+ """Get file version (windows properties)
+ :param filename: path to file
+ :return: 4-tuple with 4 version numbers
+ """
+ if not os.path.isfile(filename):
+ raise FileNotFound
+
+ try:
+ version_info = win32api.GetFileVersionInfo(filename, '\\')
+ except pywintypes.error:
+ raise VersionNotAvailable
+
+ return (divmod(version_info['FileVersionMS'], 65536) +
+ divmod(version_info['FileVersionLS'], 65536))
diff --git a/tools/win32/info.txt b/tools/win32/info.txt
new file mode 100644
index 0000000..cafc192
--- /dev/null
+++ b/tools/win32/info.txt
@@ -0,0 +1,6 @@
+Bazaar is free software; you can redistribute and/or modify it under
+the terms of the GNU General Public License version 2.
+
+See: http://www.gnu.org/copyleft/gpl.html
+
+Managing source code in Bazaar does not make it subject to the GPL.
diff --git a/tools/win32/ostools.py b/tools/win32/ostools.py
new file mode 100644
index 0000000..3d7ac5e
--- /dev/null
+++ b/tools/win32/ostools.py
@@ -0,0 +1,138 @@
+#!/usr/bin/python
+
+"""Cross-platform os tools: files/directories manipulations
+Usage:
+
+ ostools.py help
+ prints this help
+
+ ostools.py copytodir FILES... DIR
+ copy files to specified directory
+
+ ostools.py copytree FILES... DIR
+ copy files to specified directory keeping relative paths
+
+ ostools.py remove [FILES...] [DIRS...]
+ remove files or directories (recursive)
+"""
+
+import glob
+import os
+import shutil
+import sys
+
+def makedir(dirname):
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ if not os.path.isdir(dirname):
+ print "Error: Destination is not a directory", dirname
+ return 2
+ return 0
+
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv[1:]
+
+ if not argv:
+ argv = ['help']
+
+ cmd = argv.pop(0)
+
+ if cmd == 'help':
+ print __doc__
+ return 0
+
+ if cmd == 'copytodir':
+ if len(argv) < 2:
+ print "Usage: ostools.py copytodir FILES... DIR"
+ return 1
+
+ todir = argv.pop()
+ retcode = makedir(todir)
+ if retcode:
+ return retcode
+
+ files = []
+ for possible_glob in argv:
+ files += glob.glob(possible_glob)
+
+ for src in files:
+ dest = os.path.join(todir, os.path.basename(src))
+ shutil.copy(src, dest)
+ print "Copied:", src, "=>", dest
+
+ return 0
+
+ if cmd == 'copytree':
+ if len(argv) < 2:
+ print "Usage: ostools.py copytree FILES... DIR"
+ return 1
+
+ todir = argv.pop()
+ retcode = makedir(todir)
+ if retcode:
+ return retcode
+
+ files = []
+ for possible_glob in argv:
+ files += glob.glob(possible_glob)
+
+ for src in files:
+ relative_path = src
+ dest = os.path.join(todir, relative_path)
+ dest_dir = os.path.dirname(dest)
+ retcode = makedir(dest_dir)
+ if retcode:
+ return retcode
+ shutil.copy(src, dest)
+ print "Copied:", src, "=>", dest
+
+ return 0
+
+ if cmd == 'remove':
+ if len(argv) == 0:
+ print "Usage: ostools.py remove [FILES...] [DIRS...]"
+ return 1
+
+ filesdirs = []
+ for possible_glob in argv:
+ filesdirs += glob.glob(possible_glob)
+
+ for i in filesdirs:
+ if os.path.isdir(i):
+ shutil.rmtree(i)
+ print "Removed:", i
+ elif os.path.isfile(i):
+ os.remove(i)
+ print "Removed:", i
+ else:
+ print "Not found:", i
+
+ return 0
+
+ if cmd == "basename":
+ if len(argv) == 0:
+ print "Usage: ostools.py basename [PATH | URL]"
+ return 1
+
+ for path in argv:
+ print os.path.basename(path)
+ return 0
+
+ if cmd == 'makedir':
+ if len(argv) == 0:
+ print "Usage: ostools.py makedir DIR"
+ return 1
+
+ retcode = makedir(argv.pop())
+ if retcode:
+ return retcode
+ return 0
+
+ print "Usage error"
+ print __doc__
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/tools/win32/py2exe_boot_common.py b/tools/win32/py2exe_boot_common.py
new file mode 100644
index 0000000..b7af31a
--- /dev/null
+++ b/tools/win32/py2exe_boot_common.py
@@ -0,0 +1,40 @@
+# Common py2exe boot script - executed for all target types.
+
+# In the standard py2exe boot script, it setup stderr so that anything written
+# to it will be written to exe.log, and a message dialog is shown.
+# For Bazaar, we log most things to .bzr.log, and there are many things that
+# write to stderr, that are not errors, and so we don't want the py2exe dialog
+# message, So also blackhole stderr.
+
+import sys
+if sys.frozen == "windows_exe":
+ class Blackhole(object):
+ softspace = 0
+ def write(self, text):
+ pass
+ def flush(self):
+ pass
+ sys.stdout = Blackhole()
+ sys.stderr = Blackhole()
+ del Blackhole
+
+# add more directories to sys.path to allow "installing" third-party libs
+# required by some plugins (see bug #743256)
+import os
+sys.path.append(os.path.join(os.path.dirname(sys.executable), 'site-packages'))
+del os
+del sys
+
+# Disable linecache.getline() which is called by
+# traceback.extract_stack() when an exception occurs to try and read
+# the filenames embedded in the packaged python code. This is really
+# annoying on windows when the d: or e: on our build box refers to
+# someone elses removable or network drive so the getline() call
+# causes it to ask them to insert a disk in that drive.
+import linecache
+def fake_getline(filename, lineno, module_globals=None):
+ return ''
+linecache.orig_getline = linecache.getline
+linecache.getline = fake_getline
+
+del linecache, fake_getline
diff --git a/tools/win32/run_script.py b/tools/win32/run_script.py
new file mode 100644
index 0000000..9e6de84
--- /dev/null
+++ b/tools/win32/run_script.py
@@ -0,0 +1,16 @@
+# A utility that executes a script from our %PYTHON%\Scripts directory.
+# Example usage:
+# 'python run_script.py cog.py arg1 arg2'
+# which will locate %PYTHON_HOME%/Scripts/cog.py and execute it with the args.
+# This is only necessary for Windows, and only when the build process is
+# executed via a cygwin/*nix based make utility, which doesn't honor the
+# PATHEXT environment variable.
+import sys
+import os
+
+if __name__ == '__main__':
+ # clobber me, new sys.argv[0] is the script to run.
+ del sys.argv[0]
+ assert not os.path.isabs(sys.argv[0]), "If you know the FQ path, just use it!"
+ sys.argv[0] = os.path.join(sys.prefix, "Scripts", sys.argv[0])
+ execfile(sys.argv[0])
diff --git a/tools/win32/start_bzr.bat b/tools/win32/start_bzr.bat
new file mode 100644
index 0000000..2f96910
--- /dev/null
+++ b/tools/win32/start_bzr.bat
@@ -0,0 +1,30 @@
+@ECHO OFF
+
+REM ******************************************************
+REM ** You can change following environment variables **
+REM ** that affects on bzr behaviour **
+REM ******************************************************
+
+REM Add the Bzr directory to system-wide PATH environment variable
+SET PATH=C:\Program Files\Bazaar;%PATH%
+
+REM Change next line to set-up e-mail to identify yourself in bzr
+REM SET BZREMAIL=
+
+REM Change next line to specify editor to edit commit messages
+REM SET BZR_EDITOR=
+
+REM Change next line to tell where bzr should search for plugins
+REM SET BZR_PLUGIN_PATH=
+
+REM Change next line to use another home directory with bzr
+REM SET BZR_HOME=
+
+REM Change next line to control verbosity of .bzr.log
+REM SET BZR_DEBUG=30
+
+
+REM --------------------------------------------------------------------------
+
+@ECHO ON
+@bzr.exe help