aboutsummaryrefslogtreecommitdiffstats
path: root/RepSys
diff options
context:
space:
mode:
Diffstat (limited to 'RepSys')
-rw-r--r--RepSys/ConfigParser.py5
-rw-r--r--RepSys/__init__.py16
-rw-r--r--RepSys/command.py12
-rw-r--r--RepSys/commands/authoremail.py3
-rw-r--r--RepSys/commands/changed.py6
-rw-r--r--RepSys/commands/ci.py12
-rw-r--r--RepSys/commands/co.py20
-rw-r--r--RepSys/commands/create.py8
-rw-r--r--RepSys/commands/editlog.py6
-rw-r--r--RepSys/commands/getspec.py9
-rw-r--r--RepSys/commands/getsrpm.py33
-rw-r--r--RepSys/commands/markrelease.py8
-rw-r--r--RepSys/commands/patchspec.py5
-rw-r--r--RepSys/commands/putsrpm.py66
-rw-r--r--RepSys/commands/rpmlog.py36
-rw-r--r--RepSys/commands/submit.py148
-rw-r--r--RepSys/commands/switch.py7
-rw-r--r--RepSys/commands/sync.py4
-rw-r--r--RepSys/layout.py207
-rw-r--r--RepSys/log.py286
-rw-r--r--RepSys/mirror.py101
-rw-r--r--RepSys/rpmutil.py331
-rw-r--r--RepSys/svn.py551
-rw-r--r--RepSys/util.py3
24 files changed, 1287 insertions, 596 deletions
diff --git a/RepSys/ConfigParser.py b/RepSys/ConfigParser.py
index 4dc3e3c..3b4e213 100644
--- a/RepSys/ConfigParser.py
+++ b/RepSys/ConfigParser.py
@@ -350,11 +350,12 @@ class Config:
self._config = ConfigParser()
self._wrapped = {}
conffiles = []
- conffiles.append("/etc/repsys.conf")
repsys_conf = os.environ.get("REPSYS_CONF")
if repsys_conf:
conffiles.append(repsys_conf)
- conffiles.append(os.path.expanduser("~/.repsys/config"))
+ else:
+ conffiles.append("/etc/repsys.conf")
+ conffiles.append(os.path.expanduser("~/.repsys/config"))
for file in conffiles:
if os.path.isfile(file):
self._config.read(file)
diff --git a/RepSys/__init__.py b/RepSys/__init__.py
index b303065..2759e8f 100644
--- a/RepSys/__init__.py
+++ b/RepSys/__init__.py
@@ -11,20 +11,4 @@ del ConfigParser
class Error(Exception): pass
-class RepSysTree:
- """
- This class just hold methods that abstract all the not-so-explicit
- rules about the directory structure of a repsys repository.
- """
- def fixpath(cls, url):
- return re.sub("/+$", "", url)
- fixpath = classmethod(fixpath)
-
- def pkgname(cls, pkgdirurl):
- # we must remove trailling slashes in the package path because
- # os.path.basename could return "" from URLs ending with "/"
- fixedurl = cls.fixpath(pkgdirurl)
- return os.path.basename(fixedurl)
- pkgname = classmethod(pkgname)
-
# vim:et:ts=4:sw=4
diff --git a/RepSys/command.py b/RepSys/command.py
index 8029e08..f1d61f7 100644
--- a/RepSys/command.py
+++ b/RepSys/command.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
from RepSys import Error, config
-import sys, os, urllib
+import sys, os
+import urlparse
import optparse
__all__ = ["OptionParser", "do_command", "default_parent"]
@@ -39,6 +40,10 @@ def do_command(parse_options_func, main_func):
except Error, e:
sys.stderr.write("error: %s\n" % str(e))
sys.exit(1)
+ except KeyboardInterrupt:
+ sys.stderr.write("interrupted\n")
+ sys.stderr.flush()
+ sys.exit(1)
def default_parent(url):
if url.find("://") == -1:
@@ -46,8 +51,9 @@ def default_parent(url):
if not default_parent:
raise Error, "received a relative url, " \
"but default_parent was not setup"
- type, rest = urllib.splittype(default_parent)
- url = type+':'+os.path.normpath(rest+'/'+url)
+ parsed = list(urlparse.urlparse(default_parent))
+ parsed[2] = os.path.normpath(parsed[2] + "/" + url)
+ url = urlparse.urlunparse(parsed)
return url
# vim:et:ts=4:sw=4
diff --git a/RepSys/commands/authoremail.py b/RepSys/commands/authoremail.py
index aee7b58..f5b8b70 100644
--- a/RepSys/commands/authoremail.py
+++ b/RepSys/commands/authoremail.py
@@ -7,6 +7,9 @@ import getopt
HELP = """\
Usage: repsys authoremail [OPTIONS] AUTHOR
+Shows the e-mail of an SVN author. It is just a simple interface to access
+the [authors] section of repsys.conf.
+
Options:
-h Show this message
diff --git a/RepSys/commands/changed.py b/RepSys/commands/changed.py
index d3094a8..7d05604 100644
--- a/RepSys/commands/changed.py
+++ b/RepSys/commands/changed.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
from RepSys import Error
from RepSys.command import *
+from RepSys.layout import package_url
from RepSys.rpmutil import check_changed
import getopt
import sys
@@ -8,9 +9,12 @@ import sys
HELP = """\
Usage: repsys changed [OPTIONS] URL
+Shows if there are pending changes since the last package release.
+
Options:
-a Check all packages in given URL
-s Show differences
+ -M Do not use the mirror (use the main repository)
-h Show this message
Examples:
@@ -25,7 +29,7 @@ def parse_options():
opts, args = parser.parse_args()
if len(args) != 1:
raise Error, "invalid arguments"
- opts.pkgdirurl = default_parent(args[0])
+ opts.pkgdirurl = package_url(args[0])
opts.verbose = 1 # Unconfigurable
return opts
diff --git a/RepSys/commands/ci.py b/RepSys/commands/ci.py
index 9ffa3bd..8d373b5 100644
--- a/RepSys/commands/ci.py
+++ b/RepSys/commands/ci.py
@@ -5,12 +5,16 @@ from RepSys.rpmutil import commit
HELP = """\
Usage: repsys ci [TARGET]
-Will commit a change. The difference between an ordinary "svn ci" and
-"repsys ci" is that it relocates the working copy to the default repository
-in case the option "mirror" is set in repsys.conf.
+Will commit recent modifications in the package.
+
+The difference between an ordinary "svn ci" and "repsys ci" is that it
+relocates the working copy to the default repository in case the option
+"mirror" is set in repsys.conf.
Options:
-h Show this message
+ -m MSG Use the MSG as the log message
+ -F FILE Read log message from FILE
Examples:
repsys ci
@@ -20,6 +24,8 @@ Examples:
def parse_options():
parser = OptionParser(help=HELP)
parser.add_option("-m", dest="message", default=None)
+ parser.add_option("-F", dest="logfile", type="string",
+ default=None)
opts, args = parser.parse_args()
if len(args):
opts.target = args[0]
diff --git a/RepSys/commands/co.py b/RepSys/commands/co.py
index cadcf56..5349049 100644
--- a/RepSys/commands/co.py
+++ b/RepSys/commands/co.py
@@ -8,12 +8,24 @@ import sys
HELP = """\
Usage: repsys co [OPTIONS] URL [LOCALPATH]
+Checkout the package source from the Mandriva repository.
+
+If the 'mirror' option is enabled, the package is obtained from the mirror
+repository.
+
+You can specify the distro branch to checkout from by using distro/pkgname.
+
Options:
+ -d The distribution branch to checkout from
+ -b The package branch
-r REV Revision to checkout
- -o Do not use the mirror (use official server)
+ -M Do not use the mirror (use the main repository)
-h Show this message
Examples:
+ repsys co pkgname
+ repsys co -d 2009.0 pkgname
+ repsys co 2009.0/pkgame
repsys co http://repos/svn/cnc/snapshot/foo
repsys co http://repos/svn/cnc/snapshot/foo foo-pkg
"""
@@ -21,11 +33,13 @@ Examples:
def parse_options():
parser = OptionParser(help=HELP)
parser.add_option("-r", dest="revision")
- parser.add_option("-o", dest="use_mirror", default=True,
- action="store_false")
+ parser.add_option("--distribution", "-d", dest="distro", default=None)
+ parser.add_option("--branch", "-b", dest="branch", default=None)
opts, args = parser.parse_args()
if len(args) not in (1, 2):
raise Error, "invalid arguments"
+ # here we don't use package_url in order to notify the user we are
+ # using the mirror
opts.pkgdirurl = args[0]
if len(args) == 2:
opts.path = args[1]
diff --git a/RepSys/commands/create.py b/RepSys/commands/create.py
index 56af1ef..ded8abe 100644
--- a/RepSys/commands/create.py
+++ b/RepSys/commands/create.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
from RepSys import Error
from RepSys.command import *
+from RepSys.layout import package_url
from RepSys.rpmutil import create_package
import getopt
import sys
@@ -8,11 +9,14 @@ import sys
HELP = """\
Usage: repsys create [OPTIONS] URL
+Creates the minimal structure of a package in the repository.
+
Options:
-h Show this message
Examples:
- repsys create http://repos/svn/cnc/snapshot/newpkg
+ repsys create newpkg
+ repsys create svn+ssh://svn.mandriva.com/svn/packages/cooker/newpkg
"""
def parse_options():
@@ -20,7 +24,7 @@ def parse_options():
opts, args = parser.parse_args()
if len(args) != 1:
raise Error, "invalid arguments"
- opts.pkgdirurl = default_parent(args[0])
+ opts.pkgdirurl = package_url(args[0], mirrored=False)
opts.verbose = 1 # Unconfigurable
return opts
diff --git a/RepSys/commands/editlog.py b/RepSys/commands/editlog.py
index 367238f..9d1afc5 100644
--- a/RepSys/commands/editlog.py
+++ b/RepSys/commands/editlog.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
from RepSys import Error
from RepSys.command import *
+from RepSys.layout import package_url
from RepSys.svn import SVN
import re
@@ -24,14 +25,13 @@ def parse_options():
pkgdirurl, revision = "", args[0]
else:
raise Error, "invalid arguments"
- opts.pkgdirurl = default_parent(pkgdirurl)
+ opts.pkgdirurl = package_url(pkgdirurl, mirrored=False)
opts.revision = re.compile(r".*?(\d+).*").sub(r"\1", revision)
return opts
def editlog(pkgdirurl, revision):
svn = SVN()
- svn.propedit("svn:log", pkgdirurl, revision=SVN.makerev(revision),
- revprop=True)
+ svn.propedit("svn:log", pkgdirurl, rev=revision)
def main():
do_command(parse_options, editlog)
diff --git a/RepSys/commands/getspec.py b/RepSys/commands/getspec.py
index 1079a81..6a8f7ea 100644
--- a/RepSys/commands/getspec.py
+++ b/RepSys/commands/getspec.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
from RepSys import Error
from RepSys.command import *
+from RepSys.layout import package_url
from RepSys.rpmutil import get_spec
import getopt
import sys
@@ -8,12 +9,16 @@ import sys
HELP = """\
Usage: repsys getspec [OPTIONS] REPPKGURL
+Prints the .spec file of a given package.
+
Options:
-t DIR Use DIR as target for spec file (default is ".")
+ -M Do not use the mirror (use the main repository)
-h Show this message
Examples:
- repsys getspec http://repos/svn/cnc/snapshot/foo
+ repsys getspec pkgname
+ repsys getspec svn+ssh://svn.mandriva.com/svn/packages/cooker/pkgname
"""
def parse_options():
@@ -22,7 +27,7 @@ def parse_options():
opts, args = parser.parse_args()
if len(args) != 1:
raise Error, "invalid arguments"
- opts.pkgdirurl = default_parent(args[0])
+ opts.pkgdirurl = package_url(args[0])
return opts
def main():
diff --git a/RepSys/commands/getsrpm.py b/RepSys/commands/getsrpm.py
index d76aca7..8cbe1f1 100644
--- a/RepSys/commands/getsrpm.py
+++ b/RepSys/commands/getsrpm.py
@@ -5,6 +5,7 @@
#
from RepSys import Error, config
from RepSys.command import *
+from RepSys.layout import package_url
from RepSys.rpmutil import get_srpm
import tempfile
import shutil
@@ -16,20 +17,26 @@ import os
HELP = """\
Usage: repsys getsrpm [OPTIONS] REPPKGURL
+Generates the source RPM (.srpm) file of a given package.
+
Options:
- -c Use files in current/ directory (default)
- -p Use files in pristine/ directory
- -v VER Use files from the version specified by VER (e.g. 2.2.1-2cl)
- -r REV Use files from current directory, in revision REV (e.g. 1001)
- -t DIR Put SRPM file in directory DIR when done (default is ".")
- -P USER Define the RPM packager inforamtion to USER
- -s FILE Run script with "FILE TOPDIR SPECFILE" command
- -n Rename the package to include the revision number
- -l Use subversion log to build rpm %changelog
- -T FILE Template to be used to generate the %changelog
- -h Show this message
+ -c Use files in current/ directory (default)
+ -p Use files in pristine/ directory
+ -v VER Use files from the version specified by VER (e.g. 2.2.1-2cl)
+ -r REV Use files from current directory, in revision REV (e.g. 1001)
+ -t DIR Put SRPM file in directory DIR when done (default is ".")
+ -P USER Define the RPM packager inforamtion to USER
+ -s FILE Run script with "FILE TOPDIR SPECFILE" command
+ -n Rename the package to include the revision number
+ -l Use subversion log to build rpm %changelog
+ -T FILE Template to be used to generate the %changelog
+ -M Do not use the mirror (use the main repository)
+ -h Show this message
+ --strict Check if the given revision contains changes in REPPKGURL
Examples:
+ repsys getsrpm python
+ repsys getsrpm -l python
repsys getsrpm http://foo.bar/svn/cnc/snapshot/python
repsys getsrpm -p http://foo.bar/svn/cnc/releases/8cl/python
repsys getsrpm -r 1001 file:///svn/cnc/snapshot/python
@@ -69,11 +76,13 @@ def parse_options():
parser.add_option("-n", dest="revname", action="store_true")
parser.add_option("-l", dest="svnlog", action="store_true")
parser.add_option("-T", dest="template", type="string", default=None)
+ parser.add_option("--strict", dest="strict", default=False,
+ action="store_true")
opts, args = parser.parse_args()
del opts.__ignore
if len(args) != 1:
raise Error, "invalid arguments"
- opts.pkgdirurl = default_parent(args[0])
+ opts.pkgdirurl = package_url(args[0])
opts.verbose = 1
return opts
diff --git a/RepSys/commands/markrelease.py b/RepSys/commands/markrelease.py
index 440775b..057cf1d 100644
--- a/RepSys/commands/markrelease.py
+++ b/RepSys/commands/markrelease.py
@@ -9,6 +9,7 @@
#
from RepSys import Error
from RepSys.command import *
+from RepSys.layout import package_url
from RepSys.simplerpm import SRPM
from RepSys.rpmutil import mark_release
from RepSys.util import get_auth
@@ -21,6 +22,11 @@ HELP = """\
Usage: repsys markrelease [OPTIONS] REPPKGURL
+This subcommand creates a 'tag' for a given revision of a given package.
+
+The tag will be stored in the directory releases/ inside the package
+structure.
+
Options:
-f FILE Try to extract information from given file
-r REV Revision which will be used to make the release copy tag
@@ -55,7 +61,7 @@ def parse_options():
if len(args) != 1:
raise Error, "invalid arguments"
- opts.pkgdirurl = default_parent(args[0])
+ opts.pkgdirurl = package_url(args[0], mirrored=False)
filename = opts.filename
appendname = opts.appendname
diff --git a/RepSys/commands/patchspec.py b/RepSys/commands/patchspec.py
index 155ff4f..9a4881b 100644
--- a/RepSys/commands/patchspec.py
+++ b/RepSys/commands/patchspec.py
@@ -5,12 +5,15 @@
from RepSys import Error
from RepSys.rpmutil import patch_spec
from RepSys.command import *
+from RepSys.layout import package_url
import getopt
import sys
HELP = """\
Usage: repsys patchspec [OPTIONS] REPPKGURL PATCHFILE
+It will try to patch a spec file from a given package url.
+
Options:
-l LOG Use LOG as log message
-h Show this message
@@ -25,7 +28,7 @@ def parse_options():
opts, args = parser.parse_args()
if len(args) != 2:
raise Error, "invalid arguments"
- opts.pkgdirurl = default_parent(args[0])
+ opts.pkgdirurl = package_url(args[0], mirrored=False)
opts.patchfile = args[1]
return opts
diff --git a/RepSys/commands/putsrpm.py b/RepSys/commands/putsrpm.py
index 21ad234..5256cba 100644
--- a/RepSys/commands/putsrpm.py
+++ b/RepSys/commands/putsrpm.py
@@ -1,60 +1,54 @@
#!/usr/bin/python
-#
-# This program will append a release to the Conectiva Linux package
-# repository system. It's meant to be a startup system to include
-# pre-packaged SRPMS in the repository, thus, you should not commit
-# packages over an ongoing package structure (with changes in current/
-# directory and etc). Also, notice that packages must be included in
-# cronological order.
-#
from RepSys import Error
from RepSys.command import *
+from RepSys.layout import package_url
from RepSys.rpmutil import put_srpm
import getopt
import sys, os
HELP = """\
-*** WARNING --- You probably SHOULD NOT use this program! --- WARNING ***
+Usage: repsys putsrpm [OPTIONS] SOURCERPMS
-Usage: repsys putsrpm [OPTIONS] REPPKGURL
+Will import source RPMs into the SVN repository.
+
+If the package was already imported, it will add the new files and remove
+those not present in the source RPM.
Options:
- -n Append package name to provided URL
- -l LOG Use log when commiting changes
+ -m LOG Log message used when commiting changes
+ -t Create version-release tag on releases/
+ -b NAME The distribution branch to place it
+ -d URL The URL of base directory where packages will be placed
+ -c URL The URL of the base directory where the changelog will be
+ placed
+ -s Don't strip the changelog from the spec
-h Show this message
Examples:
- repsys putsrpm file://svn/cnc/snapshot/foo /cnc/d/SRPMS/foo-1.0.src.rpm
+ repsys putsrpm pkg/SRPMS/pkg-2.0-1.src.rpm
+ repsys putsrpm -b 2009.1 foo-1.1-1.src.rpm
"""
def parse_options():
parser = OptionParser(help=HELP)
- parser.add_option("-l", dest="log", default="")
- parser.add_option("-n", dest="appendname", action="store_true")
+ parser.add_option("-l", dest="logmsg", default="")
+ parser.add_option("-t", dest="markrelease", action="store_true",
+ default=False)
+ parser.add_option("-s", dest="striplog", action="store_false",
+ default=True)
+ parser.add_option("-b", dest="branch", type="string", default=None)
+ parser.add_option("-d", dest="baseurl", type="string", default=None)
+ parser.add_option("-c", dest="baseold", type="string", default=None)
opts, args = parser.parse_args()
- if len(args) != 2:
- raise Error, "invalid arguments"
- opts.pkgdirurl = default_parent(args[0])
- opts.srpmfile = args[1]
+ opts.srpmfiles = args
return opts
-def put_srpm_cmd(pkgdirurl, srpmfile, appendname=0, log=""):
- if os.path.isdir(srpmfile):
- dir = srpmfile
- for entry in os.listdir(dir):
- if entry[-8:] == ".src.rpm":
- sys.stderr.write("Putting %s... " % entry)
- sys.stderr.flush()
- entrypath = os.path.join(dir, entry)
- try:
- put_srpm(pkgdirurl, entrypath, appendname, log)
- sys.stderr.write("done\n")
- except Error, e:
- sys.stderr.write("error: %s\n" % str(e))
- else:
- put_srpm(pkgdirurl, srpmfile, appendname, log)
-
-
+def put_srpm_cmd(srpmfiles, markrelease=False, striplog=True, branch=None,
+ baseurl=None, baseold=None, logmsg=None):
+ for path in srpmfiles:
+ put_srpm(path, markrelease, striplog, branch, baseurl, baseold,
+ logmsg)
+
def main():
do_command(parse_options, put_srpm_cmd)
diff --git a/RepSys/commands/rpmlog.py b/RepSys/commands/rpmlog.py
index 7ea1ac0..11fe36d 100644
--- a/RepSys/commands/rpmlog.py
+++ b/RepSys/commands/rpmlog.py
@@ -3,23 +3,33 @@
# This program will convert the output of "svn log" to be suitable
# for usage in an rpm %changelog session.
#
-from RepSys import Error
+from RepSys import Error, layout
from RepSys.command import *
-from RepSys.log import svn2rpm
+from RepSys.svn import SVN
+from RepSys.log import get_changelog, split_spec_changelog
+from cStringIO import StringIO
import getopt
+import os
import sys
HELP = """\
Usage: repsys rpmlog [OPTIONS] REPPKGDIRURL
+Prints the RPM changelog of a given package.
+
Options:
-r REV Collect logs from given revision to revision 0
-n NUM Output only last NUM entries
-T FILE %changelog template file to be used
+ -o Append old package changelog
+ -p Append changelog found in .spec file
+ -s Sort changelog entries, even from the old log
+ -M Do not use the mirror (use the main repository)
-h Show this message
Examples:
- repsys rpmlog https://repos/snapshot/python
+ repsys rpmlog python
+ repsys rpmlog http://svn.mandriva.com/svn/packages/cooker/python
"""
def parse_options():
@@ -27,14 +37,28 @@ def parse_options():
parser.add_option("-r", dest="revision")
parser.add_option("-n", dest="size", type="int")
parser.add_option("-T", "--template", dest="template", type="string")
+ parser.add_option("-o", dest="oldlog", default=False,
+ action="store_true")
+ parser.add_option("-p", dest="usespec", default=False,
+ action="store_true")
+ parser.add_option("-s", dest="sort", default=False,
+ action="store_true")
opts, args = parser.parse_args()
if len(args) != 1:
raise Error, "invalid arguments"
- opts.pkgdirurl = default_parent(args[0])
+ opts.pkgdirurl = layout.package_url(args[0])
return opts
-def rpmlog(pkgdirurl, revision, size, template):
- sys.stdout.write(svn2rpm(pkgdirurl, revision, size, template=template))
+def rpmlog(pkgdirurl, revision, size, template, oldlog, usespec, sort):
+ another = None
+ if usespec:
+ svn = SVN()
+ specurl = layout.package_spec_url(pkgdirurl)
+ rawspec = svn.cat(specurl, rev=revision)
+ spec, another = split_spec_changelog(StringIO(rawspec))
+ newlog = get_changelog(pkgdirurl, another=another, rev=revision,
+ size=size, sort=sort, template=template, oldlog=oldlog)
+ sys.stdout.writelines(newlog)
def main():
do_command(parse_options, rpmlog)
diff --git a/RepSys/commands/submit.py b/RepSys/commands/submit.py
index 5c95526..88ff596 100644
--- a/RepSys/commands/submit.py
+++ b/RepSys/commands/submit.py
@@ -1,5 +1,5 @@
#!/usr/bin/python
-from RepSys import Error, config
+from RepSys import Error, config, layout
from RepSys.command import *
from RepSys.rpmutil import get_spec, get_submit_info
from RepSys.util import get_auth, execcmd, get_helper
@@ -7,19 +7,29 @@ import urllib
import getopt
import sys
import re
-
-#try:
-# import NINZ.client
-#except ImportError:
-# NINZ = None
+import subprocess
import xmlrpclib
HELP = """\
-Usage: repsys submit [OPTIONS] [URL [REVISION]]
+Usage: repsys submit [OPTIONS] [URL[@REVISION] ...]
Submits the package from URL to the submit host.
+The submit host will try to build the package, and upon successful
+completion will 'tag' the package and upload it to the official
+repositories.
+
+The package name can refer to an alias to a group of packages defined in
+the section submit-groups of the configuration file.
+
+The status of the submit can visualized at:
+
+http://kenobi.mandriva.com/bs/output.php
+
+If no URL and revision are specified, the latest changed revision in the
+package working copy of the current directory will be used.
+
Options:
-t TARGET Submit given package URL to given target
-l Just list available targets
@@ -28,53 +38,101 @@ Options:
-s The host in which the package URL will be submitted
(defaults to the host in the URL)
-h Show this message
+ --distro The distribution branch where the packages come from
--define Defines one variable to be used by the submit scripts
in the submit host
Examples:
repsys submit
- repsys submit foo 14800
- repsys submit https://repos/svn/mdv/cooker/foo 14800
- repsys submit -r 14800 https://repos/svn/mdv/cooker/foo
+ repsys submit foo
+ repsys submit 2009.1/foo
+ repsys submit foo@14800 bar baz@11001
+ repsys submit https://repos/svn/mdv/cooker/foo
repsys submit -l https://repos
+ repsys submit 2008.1/my-packages@11011
+ repsys submit --define section=main/testing -t 2008.1
"""
+DEFAULT_TARGET = "Cooker"
+
def parse_options():
parser = OptionParser(help=HELP)
- parser.defaults["revision"] = ""
- parser.add_option("-t", dest="target", default="Cooker")
- parser.add_option("-l", dest="list", action="store_true")
+ parser.defaults["revision"] = None
+ parser.add_option("-t", dest="target", default=None)
+ parser.add_option("-l", action="callback", callback=list_targets)
parser.add_option("-r", dest="revision", type="string", nargs=1)
parser.add_option("-s", dest="submithost", type="string", nargs=1,
default=None)
- parser.add_option("--define", action="append")
+ parser.add_option("--distro", dest="distro", type="string",
+ default=None)
+ parser.add_option("--define", action="append", default=[])
opts, args = parser.parse_args()
if not args:
- name, rev = get_submit_info(".")
- try:
- yn = raw_input("Submit '%s', revision %d (y/N)? " % (name, rev))
- except KeyboardInterrupt:
- yn = "n"
- if yn.lower() in ("y", "yes"):
- args = name, str(rev)
+ name, url, rev = get_submit_info(".")
+ args = ["%s@%s" % (url, str(rev))]
+ print "Submitting %s at revision %s" % (name, rev)
+ print "URL: %s" % url
+ if opts.revision is not None:
+ # backwards compatibility with the old -r usage
+ if len(args) == 1:
+ args[0] = args[0] + "@" + opts.revision
else:
- print "Cancelled."
- sys.exit(1)
- elif len(args) > 2:
- raise Error, "invalid arguments"
- opts.pkgdirurl = default_parent(args[0])
+ raise Error, "can't use -r REV with more than one package name"
+ del opts.revision
if len(args) == 2:
- opts.revision = re.compile(r".*?(\d+).*").sub(r"\1", args[1])
- elif len(args) == 1 and opts.revision:
- # accepts -r 3123 http://foo/bar
- pass
- elif not opts.list:
- raise Error, "provide -l or a revision number"
+ # prevent from using the old <name> <rev> syntax
+ try:
+ rev = int(args[1])
+ except ValueError:
+ # ok, it is a package name, let it pass
+ pass
+ else:
+ raise Error, "the format <name> <revision> is deprecated, "\
+ "use <name>@<revision> instead"
+ # expand group aliases
+ expanded = []
+ for nameurl in args:
+ expanded.extend(expand_group(nameurl))
+ if expanded != args:
+ print "Submitting: %s" % " ".join(expanded)
+ args = expanded
+ opts.urls = [layout.package_url(nameurl, distro=opts.distro, mirrored=False)
+ for nameurl in args]
+ if opts.target is None and opts.distro is None:
+ target = layout.distro_branch(opts.urls[0]) or DEFAULT_TARGET
+ print "Implicit target: %s" % target
+ opts.target = target
+ del opts.distro
return opts
-def submit(pkgdirurl, revision, target, list=0, define=[], submithost=None):
- #if not NINZ:
- # raise Error, "you must have NINZ installed to use this command"
+def expand_group(group):
+ name, rev = layout.split_url_revision(group)
+ distro = None
+ if "/" in name:
+ distro, name = name.rsplit("/", 1)
+ found = config.get("submit-groups", name)
+ packages = [group]
+ if found:
+ packages = found.split()
+ if rev:
+ packages = [("%s@%s" % (package, rev))
+ for package in packages]
+ if distro:
+ packages = ["%s/%s" % (distro, package)
+ for package in packages]
+ return packages
+
+def list_targets(option, opt, val, parser):
+ host = config.get("submit", "host")
+ if host is None:
+ raise Error, "no submit host defined in repsys.conf"
+ createsrpm = get_helper("create-srpm")
+ #TODO make it configurable
+ command = "ssh %s %s --list" % (host, createsrpm)
+ execcmd(command, show=True)
+ sys.exit(0)
+
+def submit(urls, target, define=[], submithost=None):
if submithost is None:
submithost = config.get("submit", "host")
if submithost is None:
@@ -86,13 +144,20 @@ def submit(pkgdirurl, revision, target, list=0, define=[], submithost=None):
del type, user, port, path, rest
# runs a create-srpm in the server through ssh, which will make a
# copy of the rpm in the export directory
- if list:
- raise Error, "unable to list targets from svn+ssh:// URLs"
createsrpm = get_helper("create-srpm")
- command = "ssh %s %s '%s' -r %s -t %s" % (
- submithost, createsrpm, pkgdirurl, revision, target)
- if define:
- command += " " + " ".join([ "--define " + x for x in define ])
+ args = ["ssh", submithost, createsrpm, "-t", target]
+ for entry in define:
+ args.append("--define")
+ args.append(entry)
+ if len(urls) == 1:
+ # be compatible with server-side repsys versions older than 1.6.90
+ url, rev = layout.split_url_revision(urls[0])
+ args.append(url)
+ args.append("-r")
+ args.append(str(rev))
+ else:
+ args.extend(urls)
+ command = subprocess.list2cmdline(args)
status, output = execcmd(command)
if status == 0:
print "Package submitted!"
@@ -100,7 +165,6 @@ def submit(pkgdirurl, revision, target, list=0, define=[], submithost=None):
sys.stderr.write(output)
sys.exit(status)
-
def main():
do_command(parse_options, submit)
diff --git a/RepSys/commands/switch.py b/RepSys/commands/switch.py
index dcbdd17..5cbe2d7 100644
--- a/RepSys/commands/switch.py
+++ b/RepSys/commands/switch.py
@@ -5,9 +5,10 @@ from RepSys.rpmutil import switch
HELP = """\
Usage: repsys switch [URL]
-Relocates the working copy to the base location URL. If URL is not
-provided, it will use the option default_parent from repsys.conf as
-default, or, if the current working copy is already based in
+Relocates the working copy to the base location URL.
+
+If URL is not provided, it will use the option default_parent from
+repsys.conf as default, or, if the current working copy is already based in
default_parent, it will use the location from the mirror option from
repsys.conf.
diff --git a/RepSys/commands/sync.py b/RepSys/commands/sync.py
index 42ede8d..a51db22 100644
--- a/RepSys/commands/sync.py
+++ b/RepSys/commands/sync.py
@@ -12,6 +12,8 @@ from the spec file.
Options:
--dry-run Print results without changing the working copy
+ --download -d
+ Try to download the source files not found
-h Show this message
Examples:
@@ -22,6 +24,8 @@ def parse_options():
parser = OptionParser(help=HELP)
parser.add_option("--dry-run", dest="dryrun", default=False,
action="store_true")
+ parser.add_option("-d", "--download", dest="download", default=False,
+ action="store_true")
opts, args = parser.parse_args()
if len(args):
opts.target = args[0]
diff --git a/RepSys/layout.py b/RepSys/layout.py
new file mode 100644
index 0000000..a4a3846
--- /dev/null
+++ b/RepSys/layout.py
@@ -0,0 +1,207 @@
+""" Handles repository layout scheme and package URLs."""
+
+import os
+import urlparse
+
+from RepSys import Error, config
+from RepSys.svn import SVN
+
+__all__ = ["package_url", "checkout_url", "repository_url", "get_url_revision"]
+
+def layout_dirs():
+ devel_branch = config.get("global", "trunk-dir", "cooker/")
+ devel_branch = os.path.normpath(devel_branch)
+ branches_dir = config.get("global", "branches-dir", "updates/")
+ branches_dir = os.path.normpath(branches_dir)
+ return devel_branch, branches_dir
+
+def get_url_revision(url, retrieve=True):
+ """Get the revision from a given URL
+
+ If the URL contains an explicit revision number (URL@REV), just use it
+ without even checking if the revision really exists.
+
+ The parameter retrieve defines whether it must ask the SVN server for
+ the revision number or not when it is not found in the URL.
+ """
+ url, rev = split_url_revision(url)
+ if rev is None and retrieve:
+ # if no revspec was found, ask the server
+ svn = SVN()
+ rev = svn.revision(url)
+ return rev
+
+def unsplit_url_revision(url, rev):
+ if rev is None:
+ newurl = url
+ else:
+ parsed = list(urlparse.urlparse(url))
+ path = os.path.normpath(parsed[2])
+ parsed[2] = path + "@" + str(rev)
+ newurl = urlparse.urlunparse(parsed)
+ return newurl
+
+def split_url_revision(url):
+ """Returns a tuple (url, rev) from an subversion URL with @REV
+
+ If the revision is not present in the URL, rev is None.
+ """
+ parsed = list(urlparse.urlparse(url))
+ path = os.path.normpath(parsed[2])
+ dirs = path.rsplit("/", 1)
+ lastname = dirs[-1]
+ newname = lastname
+ index = lastname.rfind("@")
+ rev = None
+ if index != -1:
+ newname = lastname[:index]
+ rawrev = lastname[index+1:]
+ if rawrev:
+ try:
+ rev = int(rawrev)
+ if rev < 0:
+ raise ValueError
+ except ValueError:
+ raise Error, "invalid revision specification on URL: %s" % url
+ dirs[-1] = newname
+ newpath = "/".join(dirs)
+ parsed[2] = newpath
+ newurl = urlparse.urlunparse(parsed)
+ return newurl, rev
+
+def checkout_url(pkgdirurl, branch=None, version=None, release=None,
+ releases=False, pristine=False, append_path=None):
+ """Get the URL of a branch of the package, defaults to current/
+
+ It tries to preserve revisions in the format @REV.
+ """
+ parsed = list(urlparse.urlparse(pkgdirurl))
+ path, rev = split_url_revision(parsed[2])
+ if releases:
+ path = os.path.normpath(path + "/releases")
+ elif version:
+ assert release is not None
+ path = os.path.normpath(path + "/releases/" + version + "/" + release)
+ elif pristine:
+ path = os.path.join(path, "pristine")
+ elif branch:
+ path = os.path.join(path, "branches", branch)
+ else:
+ path = os.path.join(path, "current")
+ if append_path:
+ path = os.path.join(path, append_path)
+ path = unsplit_url_revision(path, rev)
+ parsed[2] = path
+ newurl = urlparse.urlunparse(parsed)
+ return newurl
+
+def convert_default_parent(url):
+ """Removes the cooker/ component from the URL"""
+ parsed = list(urlparse.urlparse(url))
+ path = os.path.normpath(parsed[2])
+ rest, last = os.path.split(path)
+ parsed[2] = rest
+ newurl = urlparse.urlunparse(parsed)
+ return newurl
+
+def remove_current(pkgdirurl):
+ parsed = list(urlparse.urlparse(pkgdirurl))
+ path = os.path.normpath(parsed[2])
+ rest, last = os.path.split(path)
+ if last == "current":
+ # FIXME this way we will not allow packages to be named "current"
+ path = rest
+ parsed[2] = path
+ newurl = urlparse.urlunparse(parsed)
+ return newurl
+
+def repository_url(mirrored=False):
+ url = None
+ if mirrored and config.get("global", "use-mirror"):
+ url = config.get("global", "mirror")
+ if url is None:
+ url = config.get("global", "repository")
+ if not url:
+ # compatibility with the default_parent configuration option
+ default_parent = config.get("global", "default_parent")
+ if default_parent is None:
+ raise Error, "you need to set the 'repository' " \
+ "configuration option on repsys.conf"
+ url = convert_default_parent(default_parent)
+ return url
+
+def package_url(name_or_url, version=None, release=None, distro=None,
+ mirrored=True):
+ """Returns a tuple with the absolute package URL and its name
+
+ @name_or_url: name, relative path, or URL of the package. In case it is
+ a URL, the URL will just be 'normalized'.
+ @version: the version to be fetched from releases/ (requires release)
+ @release: the release number to be fetched from releases/$version/
+ @distro: the name of the repository branch inside updates/
+ @mirrored: return an URL based on the mirror repository, if enabled
+ """
+ from RepSys import mirror
+ if "://" in name_or_url:
+ pkgdirurl = mirror.normalize_path(name_or_url)
+ pkgdirurl = remove_current(pkgdirurl)
+ if mirror.using_on(pkgdirurl) and not mirrored:
+ pkgdirurl = mirror.relocate_path(mirror.mirror_url(),
+ repository_url(), pkgdirurl)
+ else:
+ name = name_or_url
+ devel_branch, branches_dir = layout_dirs()
+ if distro or "/" in name:
+ default_branch = branches_dir
+ if distro:
+ default_branch = os.path.join(default_branch, distro)
+ else:
+ default_branch = devel_branch # cooker
+ path = os.path.join(default_branch, name)
+ parsed = list(urlparse.urlparse(repository_url(mirrored=mirrored)))
+ parsed[2] = os.path.join(parsed[2], path)
+ pkgdirurl = urlparse.urlunparse(parsed)
+ return pkgdirurl
+
+def package_name(pkgdirurl):
+ """Returns the package name from a package URL
+
+ It takes care of revision numbers"""
+ parsed = urlparse.urlparse(pkgdirurl)
+ path, rev = split_url_revision(parsed[2])
+ rest, name = os.path.split(path)
+ return name
+
+def package_spec_url(pkgdirurl, *args, **kwargs):
+ """Returns the URL of the specfile of a given package URL
+
+ The parameters are the same used by checkout_url, except append_path.
+ """
+ kwargs["append_path"] = "SPECS/" + package_name(pkgdirurl) + ".spec"
+ specurl = checkout_url(pkgdirurl, *args, **kwargs)
+ return specurl
+
+def distro_branch(pkgdirurl):
+ """Tries to guess the distro branch name from a package URL"""
+ from RepSys.mirror import same_base
+ found = None
+ repo = repository_url()
+ if same_base(repo, pkgdirurl):
+ devel_branch, branches_dir = layout_dirs()
+ repo_path = urlparse.urlparse(repo)[2]
+ devel_path = os.path.join(repo_path, devel_branch)
+ branches_path = os.path.join(repo_path, branches_dir)
+ parsed = urlparse.urlparse(pkgdirurl)
+ path = os.path.normpath(parsed[2])
+ if path.startswith(devel_path):
+ # devel_branch must be before branches_dir in order to allow
+ # devel_branch to be inside branches_dir, as in /branches/cooker
+ _, found = os.path.split(devel_branch)
+ elif path.startswith(branches_path):
+ comps = path.split("/")
+ if branches_path == "/":
+ found = comps[1]
+ elif len(comps) >= 2: # must be at least branch/pkgname
+ found = comps[branches_path.count("/")+1]
+ return found
+
diff --git a/RepSys/log.py b/RepSys/log.py
index 2c69693..a1d1944 100644
--- a/RepSys/log.py
+++ b/RepSys/log.py
@@ -1,5 +1,5 @@
#!/usr/bin/python
-from RepSys import Error, config, RepSysTree
+from RepSys import Error, config, layout
from RepSys.svn import SVN
from RepSys.util import execcmd
@@ -8,30 +8,50 @@ try:
except ImportError:
raise Error, "repsys requires the package python-cheetah"
+from cStringIO import StringIO
+
import sys
import os
import re
import time
import locale
-import codecs
import glob
import tempfile
import shutil
+
+locale.setlocale(locale.LC_ALL, "C")
+
default_template = """
+#if not $releases_by_author[-1].visible
+ ## Hide the first release that contains no changes. It must be a
+ ## reimported package and the log gathered from misc/ already should
+ ## contain a correct entry for the version-release:
+ #set $releases_by_author = $releases_by_author[:-1]
+#end if
#for $rel in $releases_by_author
* $rel.date $rel.author_name <$rel.author_email> $rel.version-$rel.release
- ##
- #if not $rel.released
- (not released yet)
++ Revision: $rel.revision
+## #if not $rel.released
+##+ Status: not released
+## #end if
+ #if not $rel.visible
++ rebuild (emptylog)
#end if
#for $rev in $rel.release_revisions
#for $line in $rev.lines
- $line
+$line
#end for
#end for
#for $author in $rel.authors
+ #if not $author.visible
+ #continue
+ #end if
+ ##alternatively, one could use:
+ ###if $author.email == "root"
+ ## #continue
+ ###end if
+ $author.name <$author.email>
#for $rev in $author.revisions
#for $line in $rev.lines
@@ -50,14 +70,13 @@ def getrelease(pkgdirurl, rev=None, macros=[], exported=None):
Is here where things should be changed if "automatic release increasing"
will be used.
"""
- svn = SVN()
from RepSys.rpmutil import rpm_macros_defs
- tmpdir = tempfile.mktemp()
+ svn = SVN()
pkgcurrenturl = os.path.join(pkgdirurl, "current")
specurl = os.path.join(pkgcurrenturl, "SPECS")
if exported is None:
tmpdir = tempfile.mktemp()
- svn.export(specurl, tmpdir, revision=SVN.makerev(rev))
+ svn.export(specurl, tmpdir, rev=rev)
else:
tmpdir = os.path.join(exported, "SPECS")
try:
@@ -89,8 +108,7 @@ def getrelease(pkgdirurl, rev=None, macros=[], exported=None):
if exported is None and os.path.isdir(tmpdir):
shutil.rmtree(tmpdir)
-
-class ChangelogRevision:
+class _Revision:
lines = []
date = None
raw_date = None
@@ -103,11 +121,12 @@ class ChangelogRevision:
def __repr__(self):
lines = repr(self.lines)[:30] + "...]"
- line = "<ChangelogRevision %d author=%r date=%r lines=%s>" % \
+ line = "<_Revision %d author=%r date=%r lines=%s>" % \
(self.revision, self.author, self.date, lines)
return line
-class ChangelogRelease(ChangelogRevision):
+
+class _Release(_Revision):
version = None
release = None
revisions = []
@@ -116,11 +135,11 @@ class ChangelogRelease(ChangelogRevision):
visible = False
def __init__(self, **kwargs):
- ChangelogRevision.__init__(self, **kwargs)
self.revisions = []
+ _Revision.__init__(self, **kwargs)
def __repr__(self):
- line = "<ChangelogRelease v=%s r=%s revs=%r>" % \
+ line = "<_Release v=%s r=%s revs=%r>" % \
(self.version, self.release, self.revisions)
return line
@@ -153,59 +172,62 @@ def format_lines(lines):
return entrylines
-class ChangelogByAuthor:
+class _Author:
name = None
email = None
revisions = None
+ visible = False
def group_releases_by_author(releases):
allauthors = []
grouped = []
for release in releases:
+
+ # group revisions of the release by author
authors = {}
latest = None
for revision in release.revisions:
authors.setdefault(revision.author, []).append(revision)
- # all the mess below is to sort by author and by revision number
+ # create _Authors and sort them by their latest revisions
decorated = []
for authorname, revs in authors.iteritems():
- author = ChangelogByAuthor()
+ author = _Author()
author.name = revs[0].author_name
author.email = revs[0].author_email
- revdeco = [(r.revision, r) for r in revs]
- revdeco.sort(reverse=1)
- author.revisions = [t[1] for t in revdeco]
+ author.revisions = revs
+ # #41117: mark those authors without visible messages
+ author.visible = bool(sum(len(rev.lines) for rev in revs))
revlatest = author.revisions[0]
- # keep the latest revision even for silented authors (below)
+ # keep the latest revision even for completely invisible
+ # authors (below)
if latest is None or revlatest.revision > latest.revision:
latest = revlatest
- count = sum(len(rev.lines) for rev in author.revisions)
- if count == 0:
- # skipping author with only silented lines
+ if not author.visible:
+ # only sort those visible authors, invisible ones are used
+ # only in "latest"
continue
- decorated.append((revdeco[0][0], author))
+ decorated.append((revlatest.revision, author))
+ decorated.sort(reverse=1)
- if not decorated:
- # skipping release with only authors with silented lines
- continue
+ if release.visible:
+ release.authors = [t[1] for t in decorated]
+ firstrel, release.authors = release.authors[0], release.authors[1:]
+ release.author_name = firstrel.name
+ release.author_email = firstrel.email
+ release.release_revisions = firstrel.revisions
+ else:
+ # we don't care about other possible authors in completely
+ # invisible releases
+ firstrev = release.revisions[0]
+ release.author_name = firstrev.author_name
+ release.author_email = firstrev.author_email
+ release.raw_date = firstrev.raw_date
+ release.date = firstrev.date
- decorated.sort(reverse=1)
- release.authors = [t[1] for t in decorated]
- # the difference between a released and a not released _Release is
- # the way the release numbers is obtained. So, when this is a
- # released, we already have it, but if we don't, we should get de
- # version/release string using getrelease and then get the first
- first, release.authors = release.authors[0], release.authors[1:]
- release.author_name = first.name
- release.author_email = first.email
- release.release_revisions = first.revisions
-
- #release.date = first.revisions[0].date
release.date = latest.date
release.raw_date = latest.raw_date
- #release.revision = first.revisions[0].revision
release.revision = latest.revision
grouped.append(release)
@@ -217,7 +239,7 @@ def group_revisions_by_author(currentlog):
revisions = []
last_author = None
for entry in currentlog:
- revision = ChangelogRevision()
+ revision = _Revision()
revision.lines = format_lines(entry.lines)
revision.raw_date = entry.date
revision.date = parse_raw_date(entry.date)
@@ -225,7 +247,7 @@ def group_revisions_by_author(currentlog):
if entry.author == last_author:
revisions[-1].revisions.append(revision)
else:
- author = ChangelogByAuthor()
+ author = _Author()
author.name, author.email = get_author_name(entry.author)
author.revisions = [revision]
revisions.append(author)
@@ -269,7 +291,7 @@ def filter_log_lines(lines):
def make_release(author=None, revision=None, date=None, lines=None,
entries=[], released=True, version=None, release=None):
- rel = ChangelogRelease()
+ rel = _Release()
rel.author = author
if author:
rel.author_name, rel.author_email = get_author_name(author)
@@ -282,11 +304,11 @@ def make_release(author=None, revision=None, date=None, lines=None,
rel.visible = False
for entry in entries:
lines = filter_log_lines(entry.lines)
- if lines:
- rel.visible = True
- revision = ChangelogRevision()
+ revision = _Revision()
revision.revision = entry.revision
revision.lines = format_lines(lines)
+ if revision.lines:
+ rel.visible = True
revision.date = parse_raw_date(entry.date)
revision.raw_date = entry.date
revision.author = entry.author
@@ -297,7 +319,8 @@ def make_release(author=None, revision=None, date=None, lines=None,
def dump_file(releases, currentlog=None, template=None):
- templpath = template or config.get("template", "path", None)
+ templpath = template or config.get("template", "path",
+ "/usr/share/repsys/default.chlog")
params = {}
if templpath is None or not os.path.exists(templpath):
params["source"] = default_template
@@ -311,12 +334,7 @@ def dump_file(releases, currentlog=None, template=None):
"releases" : releases,
"revisions_by_author": revisions_author}]
t = Template(**params)
- chlog = t.respond()
- try:
- chlog = chlog.decode("utf8")
- except UnicodeError:
- pass
- return chlog
+ return t.respond()
class InvalidEntryError(Exception):
@@ -379,18 +397,14 @@ def parse_markrelease_log(relentry):
def svn2rpm(pkgdirurl, rev=None, size=None, submit=False,
template=None, macros=[], exported=None):
- size = size or 0
concat = config.get("log", "concat", "").split()
revoffset = get_revision_offset()
svn = SVN()
- pkgreleasesurl = os.path.join(pkgdirurl, "releases")
- pkgcurrenturl = os.path.join(pkgdirurl, "current")
- releaseslog = list(svn.log(pkgreleasesurl,
- strict_node_history=False, noerror=1)) or []
- currentlog = list(svn.log(pkgcurrenturl,
- strict_node_history=False,
- revision_start=SVN.makerev(rev),
- revision_end=SVN.makerev(revoffset), limit=size))
+ pkgreleasesurl = layout.checkout_url(pkgdirurl, releases=True)
+ pkgcurrenturl = layout.checkout_url(pkgdirurl)
+ releaseslog = svn.log(pkgreleasesurl, noerror=1)
+ currentlog = svn.log(pkgcurrenturl, limit=size, start=rev,
+ end=revoffset)
# sort releases by copyfrom-revision, so that markreleases for same
# revisions won't look empty
@@ -454,40 +468,74 @@ def svn2rpm(pkgdirurl, rev=None, size=None, submit=False,
data = dump_file(releases[::-1], currentlog=currentlog, template=template)
return data
-
-
-def specfile_svn2rpm(pkgdirurl, specfile, rev=None, size=None,
- submit=False, template=None, macros=[], exported=None):
- newlines = []
+def _split_changelog(stream):
+ current = None
+ count = 0
+ def finish(entry):
+ lines = entry[2]
+ # strip newlines at the end
+ for i in xrange(len(lines)-1, -1, -1):
+ if lines[i] != "\n":
+ break
+ del lines[i]
+ return entry
+ for line in stream:
+ if line.startswith("*"):
+ if current:
+ yield finish(current)
+ fields = line.split()
+ rawdate = " ".join(fields[:5])
+ try:
+ date = time.strptime(rawdate, "* %a %b %d %Y")
+ except ValueError, e:
+ raise Error, "failed to parse spec changelog: %s" % e
+ curlines = [line]
+ current = (date, count, curlines)
+ # count used to ensure stable sorting when changelog entries
+ # have the same date, otherwise it would also compare the
+ # changelog lines
+ count -= 1
+ elif current:
+ curlines.append(line)
+ else:
+ pass # not good, but ignore
+ if current:
+ yield finish(current)
+
+def sort_changelog(stream):
+ entries = _split_changelog(stream)
+ log = StringIO()
+ for time, count, elines in sorted(entries, reverse=True):
+ log.writelines(elines)
+ log.write("\n")
+ return log
+
+def split_spec_changelog(stream):
+ chlog = StringIO()
+ spec = StringIO()
found = 0
-
- encoding = locale.getpreferredencoding()
-
- def open(name, mode="r"):
- return codecs.open(name, mode, encoding, errors="replace")
-
- # Strip old changelogs
- for line in open(specfile):
+ for line in stream:
if line.startswith("%changelog"):
found = 1
elif not found:
- newlines.append(line)
+ spec.write(line)
+ elif found:
+ chlog.write(line)
elif line.startswith("%"):
found = 0
- newlines.append(line)
-
- # Create new changelog
- newlines.append("\n\n%changelog\n")
- newlines.append(svn2rpm(pkgdirurl, rev=rev, size=size, submit=submit,
- template=template, macros=macros, exported=exported))
+ spec.write(line)
+ spec.seek(0)
+ chlog.seek(0)
+ return spec, chlog
- # Merge old changelog, if available
+def get_old_log(pkgdirurl):
+ chlog = StringIO()
oldurl = config.get("log", "oldurl")
if oldurl:
svn = SVN()
tmpdir = tempfile.mktemp()
try:
- pkgname = RepSysTree.pkgname(pkgdirurl)
+ pkgname = layout.package_name(pkgdirurl)
pkgoldurl = os.path.join(oldurl, pkgname)
try:
# we're using HEAD here because fixes in misc/ (oldurl) may
@@ -499,20 +547,74 @@ def specfile_svn2rpm(pkgdirurl, specfile, rev=None, size=None,
logfile = os.path.join(tmpdir, "log")
if os.path.isfile(logfile):
file = open(logfile)
- newlines.append("\n")
+ chlog.write("\n") # TODO needed?
log = file.read()
log = escape_macros(log)
- newlines.append(log)
+ chlog.write(log)
file.close()
finally:
if os.path.isdir(tmpdir):
shutil.rmtree(tmpdir)
+ chlog.seek(0)
+ return chlog
- # Write new specfile
- file = open(specfile, "w")
- file.write("".join(newlines))
- file.close()
+def get_changelog(pkgdirurl, another=None, svn=True, rev=None, size=None,
+ submit=False, sort=False, template=None, macros=[], exported=None,
+ oldlog=False):
+ """Generates the changelog for a given package URL
+
+ @another: a stream with the contents of a changelog to be merged with
+ the one generated
+ @svn: enable changelog from svn
+ @rev: generate the changelog with the changes up to the given
+ revision
+ @size: the number of revisions to be used (as in svn log --limit)
+ @submit: defines whether the latest unreleased log entries should have
+ the version parsed from the spec file
+ @sort: should changelog entries be reparsed and sorted after appending
+ the oldlog?
+ @template: the path to the cheetah template used to generate the
+ changelog from svn
+ @macros: a list of tuples containing macros to be defined when
+ parsing the version in the changelog
+ @exported: the path of a directory containing an already existing
+ checkout of the package, so that the spec file can be
+ parsed from there
+ @oldlog: if set it will try to append the old changelog file defined
+ in oldurl in repsys.conf
+ """
+ newlog = StringIO()
+ if svn:
+ rawsvnlog = svn2rpm(pkgdirurl, rev=rev, size=size, submit=submit,
+ template=template, macros=macros, exported=exported)
+ newlog.write(rawsvnlog)
+ if another:
+ newlog.writelines(another)
+ if oldlog:
+ newlog.writelines(get_old_log(pkgdirurl))
+ if sort:
+ newlog.seek(0)
+ newlog = sort_changelog(newlog)
+ newlog.seek(0)
+ return newlog
+def specfile_svn2rpm(pkgdirurl, specfile, rev=None, size=None,
+ submit=False, sort=False, template=None, macros=[], exported=None):
+ fi = open(specfile)
+ spec, oldchlog = split_spec_changelog(fi)
+ fi.close()
+ another = None
+ if config.getbool("log", "merge-spec", False):
+ another = oldchlog
+ sort = sort or config.getbool("log", "sort", False)
+ chlog = get_changelog(pkgdirurl, another=another, rev=rev, size=size,
+ submit=submit, sort=sort, template=template, macros=macros,
+ exported=exported, oldlog=True)
+ fo = open(specfile, "w")
+ fo.writelines(spec)
+ fo.write("\n\n%changelog\n")
+ fo.writelines(chlog)
+ fo.close()
if __name__ == "__main__":
l = svn2rpm(sys.argv[1])
diff --git a/RepSys/mirror.py b/RepSys/mirror.py
index 1e9e9c2..f0d2316 100644
--- a/RepSys/mirror.py
+++ b/RepSys/mirror.py
@@ -1,87 +1,104 @@
+import sys
import os
import urlparse
+import urllib
-from RepSys import Error, config
+from RepSys import Error, config, layout
from RepSys.svn import SVN
-def _normdirurl(url):
+def mirror_url():
+ mirror = config.get("global", "mirror")
+ return mirror
+
+def normalize_path(url):
"""normalize url for relocate_path needs"""
parsed = urlparse.urlparse(url)
- path = os.path.normpath(parsed.path)
- newurl = urlparse.urlunparse((parsed.scheme, parsed.netloc, path,
- parsed.params, parsed.query, parsed.fragment))
+ path = os.path.normpath(parsed[2])
+ newurl = urlparse.urlunparse((parsed[0], parsed[1], path,
+ parsed[3], parsed[4], parsed[5]))
return newurl
def _joinurl(url, relpath):
parsed = urlparse.urlparse(url)
- newpath = os.path.join(parsed.path, relpath)
- newurl = urlparse.urlunparse((parsed.scheme, parsed.netloc, newpath,
- parsed.params, parsed.query, parsed.fragment))
+ newpath = os.path.join(parsed[2], relpath)
+ newurl = urlparse.urlunparse((parsed[0], parsed[1], newpath,
+ parsed[3], parsed[4], parsed[5]))
+ return newurl
+
+
+def strip_username(url):
+ parsed = list(urlparse.urlparse(url))
+ _, parsed[1] = urllib.splituser(parsed[1])
+ newurl = urlparse.urlunparse(parsed)
return newurl
def same_base(parent, url):
"""returns true if parent is parent of url"""
- parent = _normdirurl(parent)
- url = _normdirurl(url)
- #FIXME handle paths with/without username/password
+ parent = normalize_path(parent)
+ url = normalize_path(url)
+ url = strip_username(url)
return url.startswith(parent)
def relocate_path(oldparent, newparent, url):
- oldparent = _normdirurl(oldparent)
- newparent = _normdirurl(newparent)
- url = _normdirurl(url)
+ oldparent = normalize_path(oldparent)
+ newparent = normalize_path(newparent)
+ url = normalize_path(url)
subpath = url[len(oldparent)+1:]
newurl = _joinurl(newparent, subpath) # subpath usually gets / at begining
return newurl
def enabled(wcurl=None):
- mirror = config.get("global", "mirror")
- default_parent = config.get("global", "default_parent")
+ mirror = mirror_url()
+ repository = layout.repository_url()
enabled = False
- if mirror and default_parent:
+ if mirror and repository:
enabled = True
- if wcurl and (not same_base(mirror, wcurl)):
+ if wcurl and not same_base(mirror, wcurl):
enabled = False
return enabled
+def using_on(url):
+ """returnes True if the URL points to the mirror repository"""
+ mirror = mirror_url()
+ if mirror:
+ using = same_base(mirror, url)
+ else:
+ using = False
+ return using
+
+def info(url, stream=sys.stderr):
+ if using_on(url):
+ stream.write("using mirror\n")
+
def mirror_relocate(oldparent, newparent, url, wcpath):
svn = SVN()
newurl = relocate_path(oldparent, newparent, url)
- # note that svn.relocate requires paths without trailling slashes,
- # http://foo/svn/bar/baz/ will fail
- svn.relocate(url, newurl, wcpath)
+ svn.switch(newurl, url, path=wcpath, relocate=True)
return newurl
def switchto_parent(svn, url, path):
"""Relocates the working copy to default_parent"""
- mirror = config.get("global", "mirror")
- default_parent = config.get("global", "default_parent")
- newurl = mirror_relocate(mirror, default_parent, url, path)
+ newurl = mirror_relocate(mirror_url(), layout.repository_url(), url, path)
return newurl
-def switchto_mirror(svn, url, path):
- mirror = config.get("global", "mirror")
- default_parent = config.get("global", "default_parent")
- newurl = mirror_relocate(default_parent, mirror, url, path)
+def switchto_parent_url(url):
+ newurl = relocate_path(mirror_url(), layout.repository_url(), url)
return newurl
-def checkout_url(url):
- mirror = config.get("global", "mirror")
- default_parent = config.get("global", "default_parent")
- if mirror is not None and default_parent is not None:
- return relocate_path(default_parent, mirror, url)
- return url
+def switchto_mirror(svn, url, path):
+ newurl = mirror_relocate(layout.repository_url(), mirror_url(), url, path)
+ return newurl
def autoswitch(svn, wcpath, wcurl, newbaseurl=None):
"""Switches between mirror, default_parent, or newbaseurl"""
nobase = False
- mirror = config.get("global", "mirror")
- default_parent = config.get("global", "default_parent")
- current = default_parent
- if default_parent is None:
- raise Error, "the option default_parent from repsys.conf is "\
+ mirror = mirror_url()
+ repository = layout.repository_url()
+ current = repository
+ if repository is None:
+ raise Error, "the option repository from repsys.conf is "\
"required"
- indefault = same_base(default_parent, wcurl)
+ indefault = same_base(repository, wcurl)
if not newbaseurl:
if not mirror:
raise Error, "an URL is needed when the option mirror "\
@@ -90,7 +107,7 @@ def autoswitch(svn, wcpath, wcurl, newbaseurl=None):
chosen = mirror
elif same_base(mirror, wcurl):
current = mirror
- chosen = default_parent
+ chosen = repository
else:
nobase = True
else:
@@ -103,7 +120,7 @@ def autoswitch(svn, wcpath, wcurl, newbaseurl=None):
chosen = newbaseurl
if nobase:
raise Error, "the URL of this working copy is not based in "\
- "default_parent nor mirror URLs"
+ "repository nor mirror URLs"
assert current != chosen
newurl = mirror_relocate(current, chosen, wcurl, wcpath)
return newurl
diff --git a/RepSys/rpmutil.py b/RepSys/rpmutil.py
index 4cbba62..0ca6bdb 100644
--- a/RepSys/rpmutil.py
+++ b/RepSys/rpmutil.py
@@ -1,15 +1,15 @@
#!/usr/bin/python
-from RepSys import Error, config, RepSysTree
-from RepSys import mirror
+from RepSys import Error, config
+from RepSys import mirror, layout, log
from RepSys.svn import SVN
from RepSys.simplerpm import SRPM
-from RepSys.log import specfile_svn2rpm
from RepSys.util import execcmd
-import pysvn
from RepSys.command import default_parent
import rpm
+import urlparse
import tempfile
import shutil
+import string
import glob
import sys
import os
@@ -18,8 +18,8 @@ def get_spec(pkgdirurl, targetdir=".", submit=False):
svn = SVN()
tmpdir = tempfile.mktemp()
try:
- geturl = "/".join([pkgdirurl, "current", "SPECS"])
- svn.export("%s" % geturl, tmpdir)
+ geturl = layout.checkout_url(pkgdirurl, append_path="SPECS")
+ svn.export("'%s'" % geturl, tmpdir)
speclist = glob.glob(os.path.join(tmpdir, "*.spec"))
if not speclist:
raise Error, "no spec files found"
@@ -34,6 +34,23 @@ def rpm_macros_defs(macros):
args = " ".join(defs)
return args
+#FIXME move it to another module
+def rev_touched_url(url, rev):
+ svn = SVN()
+ info = svn.info2(url)
+ if info is None:
+ raise Error, "can't fetch svn info about the URL: %s" % url
+ root = info["Repository Root"]
+ urlpath = url[len(root):]
+ touched = False
+ entries = svn.log(root, start=rev, limit=1)
+ entry = entries[0]
+ for change in entry.changed:
+ path = change.get("path")
+ if path and path.startswith(urlpath):
+ touched = True
+ return touched
+
def get_srpm(pkgdirurl,
mode = "current",
targetdirs = None,
@@ -47,7 +64,8 @@ def get_srpm(pkgdirurl,
submit = False,
template = None,
macros = [],
- verbose = 0):
+ verbose = 0,
+ strict = False):
svn = SVN()
tmpdir = tempfile.mktemp()
topdir = "--define '_topdir %s'" % tmpdir
@@ -57,17 +75,26 @@ def get_srpm(pkgdirurl,
specdir = "--define '_specdir %s/%s'" % (tmpdir, "SPECS")
srcrpmdir = "--define '_srcrpmdir %s/%s'" % (tmpdir, "SRPMS")
patchdir = "--define '_patchdir %s/%s'" % (tmpdir, "SOURCES")
+
try:
if mode == "version":
- geturl = os.path.join(pkgdirurl, "releases",
- version, release)
+ geturl = layout.checkout_url(pkgdirurl, version=version,
+ release=release)
elif mode == "pristine":
- geturl = os.path.join(pkgdirurl, "pristine")
+ geturl = layout.checkout_url(pkgdirurl, pristine=True)
elif mode == "current" or mode == "revision":
- geturl = os.path.join(pkgdirurl, "current")
+ #FIXME we should handle revisions specified using @REV
+ geturl = layout.checkout_url(pkgdirurl)
else:
raise Error, "unsupported get_srpm mode: %s" % mode
- exportedrev = svn.export(geturl, tmpdir, revision=SVN.makerev(revision))
+ strict = strict or config.getbool("submit", "strict-revision", False)
+ if strict and not rev_touched_url(geturl, revision):
+ #FIXME would be nice to have the revision number even when
+ # revision is None
+ raise Error, "the revision %s does not change anything "\
+ "inside %s" % (revision or "HEAD", geturl)
+ mirror.info(geturl)
+ svn.export(geturl, tmpdir, rev=revision)
srpmsdir = os.path.join(tmpdir, "SRPMS")
os.mkdir(srpmsdir)
specsdir = os.path.join(tmpdir, "SPECS")
@@ -77,7 +104,7 @@ def get_srpm(pkgdirurl,
spec = speclist[0]
if svnlog:
submit = not not revision
- specfile_svn2rpm(pkgdirurl, spec, revision, submit=submit,
+ log.specfile_svn2rpm(pkgdirurl, spec, revision, submit=submit,
template=template, macros=macros, exported=tmpdir)
for script in scripts:
#FIXME revision can be "None"
@@ -89,39 +116,46 @@ def get_srpm(pkgdirurl,
packager = " --define 'packager %s'" % packager
defs = rpm_macros_defs(macros)
- execcmd("rpm -bs --nodeps %s %s %s %s %s %s %s %s %s %s" %
- (topdir, builddir, rpmdir, sourcedir, specdir,
- srcrpmdir, patchdir, packager, spec, defs))
-
- if revision:
- #FIXME duplicate glob line
- srpm = glob.glob(os.path.join(srpmsdir, "*.src.rpm"))[0]
- srpminfo = SRPM(srpm)
- release = srpminfo.release
- srpmbase = os.path.basename(srpm)
- os.rename(srpm, "%s/@%s:%s" % (srpmsdir, exportedrev.number, srpmbase))
- srpm = glob.glob(os.path.join(srpmsdir, "*.src.rpm"))[0]
+ sourcecmd = config.get("helper", "rpmbuild", "rpmbuild")
+ execcmd("%s -bs --nodeps %s %s %s %s %s %s %s %s %s %s" %
+ (sourcecmd, topdir, builddir, rpmdir, sourcedir, specdir,
+ srcrpmdir, patchdir, packager, spec, defs))
+
+ # copy the generated SRPMs to their target locations
+ targetsrpms = []
+ urlrev = None
+ if revname:
+ urlrev = revision or layout.get_url_revision(geturl)
if not targetdirs:
targetdirs = (".",)
- targetsrpms = []
- for targetdir in targetdirs:
- targetsrpm = os.path.join(os.path.realpath(targetdir),
- os.path.basename(srpm))
- targetsrpms.append(targetsrpm)
- if verbose:
- sys.stderr.write("Wrote: %s\n" % targetsrpm)
- execcmd("cp -f", srpm, targetdir)
- os.unlink(srpm)
+ srpms = glob.glob(os.path.join(srpmsdir, "*.src.rpm"))
+ if not srpms:
+ # something fishy happened
+ raise Error, "no SRPMS were found at %s" % srpmsdir
+ for srpm in srpms:
+ name = os.path.basename(srpm)
+ if revname:
+ name = "@%s:%s" % (urlrev, name)
+ for targetdir in targetdirs:
+ newpath = os.path.join(targetdir, name)
+ targetsrpms.append(newpath)
+ if os.path.exists(newpath):
+ # should we warn?
+ os.unlink(newpath)
+ shutil.copy(srpm, newpath)
+ if verbose:
+ sys.stderr.write("Wrote: %s\n" % newpath)
return targetsrpms
finally:
if os.path.isdir(tmpdir):
shutil.rmtree(tmpdir)
def patch_spec(pkgdirurl, patchfile, log=""):
+ #FIXME use get_spec
svn = SVN()
tmpdir = tempfile.mktemp()
try:
- geturl = "/".join([pkgdirurl, "current", "SPECS"])
+ geturl = layout.checkout_url(pkgdirurl, append_path="SPECS")
svn.checkout(geturl, tmpdir)
speclist = glob.glob(os.path.join(tmpdir, "*.spec"))
if not speclist:
@@ -131,39 +165,46 @@ def patch_spec(pkgdirurl, patchfile, log=""):
if status != 0:
raise Error, "can't apply patch:\n%s\n" % output
else:
- svn.checkin(tmpdir, log=log)
+ svn.commit(tmpdir, log="")
finally:
if os.path.isdir(tmpdir):
shutil.rmtree(tmpdir)
-def put_srpm(pkgdirurl, srpmfile, appendname=0, log=""):
- srpm = SRPM(srpmfile)
- if appendname:
- pkgdirurl = "/".join([pkgdirurl, srpm.name])
+def put_srpm(srpmfile, markrelease=False, striplog=True, branch=None,
+ baseurl=None, baseold=None, logmsg=None):
svn = SVN()
+ srpm = SRPM(srpmfile)
tmpdir = tempfile.mktemp()
+ if baseurl:
+ pkgurl = mirror._joinurl(baseurl, srpm.name)
+ else:
+ pkgurl = layout.package_url(srpm.name, distro=branch,
+ mirrored=False)
+ print "Importing package to %s" % pkgurl
try:
if srpm.epoch:
version = "%s:%s" % (srpm.epoch, srpm.version)
else:
version = srpm.version
- versionurl = "/".join([pkgdirurl, "releases", version])
+ versionurl = "/".join([pkgurl, "releases", version])
releaseurl = "/".join([versionurl, srpm.release])
- ret = svn.mkdir(pkgdirurl, "Created package directory", noerror=1)
- if ret:
- svn.checkout(pkgdirurl, tmpdir)
+ currenturl = os.path.join(tmpdir, "current")
+ #FIXME when pre-commit hook fails, there's no clear way to know
+ # what happened
+ ret = svn.mkdir(pkgurl, noerror=1, log="Created package directory")
+ if ret or not svn.ls(currenturl, noerror=1):
+ svn.checkout(pkgurl, tmpdir)
svn.mkdir(os.path.join(tmpdir, "releases"))
- svn.mkdir(os.path.join(tmpdir, "releases", version))
- svn.mkdir(os.path.join(tmpdir, "current"))
- svn.mkdir(os.path.join(tmpdir, "current", "SPECS"))
- svn.mkdir(os.path.join(tmpdir, "current", "SOURCES"))
+ svn.mkdir(currenturl)
+ svn.mkdir(os.path.join(currenturl, "SPECS"))
+ svn.mkdir(os.path.join(currenturl, "SOURCES"))
#svn.commit(tmpdir,log="Created package structure.")
version_exists = 1
currentdir = os.path.join(tmpdir, "current")
else:
- if svn.exists(releaseurl):
+ if svn.ls(releaseurl, noerror=1):
raise Error, "release already exists"
- svn.checkout("/".join([pkgdirurl, "current"]), tmpdir)
+ svn.checkout("/".join([pkgurl, "current"]), tmpdir)
svn.mkdir(versionurl, noerror=1,
log="Created directory for version %s." % version)
currentdir = tmpdir
@@ -217,28 +258,56 @@ def put_srpm(pkgdirurl, srpmfile, appendname=0, log=""):
if os.path.isdir(unpackdir):
shutil.rmtree(unpackdir)
- svn.checkin(tmpdir, log=log)
+ if striplog:
+ specs = glob.glob(os.path.join(specsdir, "*.spec"))
+ if not specs:
+ raise Error, "no spec file fount on %s" % specsdir
+ specpath = specs[0]
+ fspec = open(specpath)
+ spec, chlog = log.split_spec_changelog(fspec)
+ chlog.seek(0)
+ fspec.close()
+ oldurl = baseold or config.get("log", "oldurl")
+ pkgoldurl = mirror._joinurl(oldurl, srpm.name)
+ svn.mkdir(pkgoldurl, noerror=1,
+ log="created old log directory for %s" % srpm.name)
+ logtmp = tempfile.mktemp()
+ try:
+ svn.checkout(pkgoldurl, logtmp)
+ miscpath = os.path.join(logtmp, "log")
+ fmisc = open(miscpath, "w+")
+ fmisc.writelines(chlog)
+ fmisc.close()
+ svn.add(miscpath)
+ svn.commit(logtmp,
+ log="imported old log for %s" % srpm.name)
+ finally:
+ if os.path.isdir(logtmp):
+ shutil.rmtree(logtmp)
+ svn.commit(tmpdir,
+ log=logmsg or ("imported package %s" % srpm.name))
finally:
if os.path.isdir(tmpdir):
shutil.rmtree(tmpdir)
# Do revision and pristine tag copies
- pristineurl = os.path.join(pkgdirurl, "pristine")
+ pristineurl = layout.checkout_url(pkgurl, pristine=True)
svn.remove(pristineurl, noerror=1,
log="Removing previous pristine/ directory.")
- currenturl = os.path.join(pkgdirurl, "current")
+ currenturl = layout.checkout_url(pkgurl)
svn.copy(currenturl, pristineurl,
log="Copying release %s-%s to pristine/ directory." %
(version, srpm.release))
- svn.copy(currenturl, releaseurl,
- log="Copying release %s-%s to releases/ directory." %
- (version, srpm.release))
+ if markrelease:
+ svn.copy(currenturl, releaseurl,
+ log="Copying release %s-%s to releases/ directory." %
+ (version, srpm.release))
def create_package(pkgdirurl, log="", verbose=0):
svn = SVN()
tmpdir = tempfile.mktemp()
try:
- basename = RepSysTree.pkgname(pkgdirurl)
+ basename = layout.package_name(pkgdirurl)
if verbose:
print "Creating package directory...",
sys.stdout.flush()
@@ -257,7 +326,7 @@ def create_package(pkgdirurl, log="", verbose=0):
if verbose:
print "done"
print "Committing...",
- svn.checkin(tmpdir,
+ svn.commit(tmpdir,
log="Created package structure for '%s'." % basename)
print "done"
finally:
@@ -280,21 +349,21 @@ def mark_release(pkgdirurl, version, release, revision):
releasesurl = "/".join([pkgdirurl, "releases"])
versionurl = "/".join([releasesurl, version])
releaseurl = "/".join([versionurl, release])
- if svn.exists(releaseurl, noerror=1):
+ if svn.ls(releaseurl, noerror=1):
raise Error, "release already exists"
svn.mkdir(releasesurl, noerror=1,
log="Created releases directory.")
svn.mkdir(versionurl, noerror=1,
log="Created directory for version %s." % version)
- pristineurl = os.path.join(pkgdirurl, "pristine")
+ pristineurl = layout.checkout_url(pkgdirurl, pristine=True)
svn.remove(pristineurl, noerror=1,
log="Removing previous pristine/ directory.")
- currenturl = os.path.join(pkgdirurl, "current")
+ currenturl = layout.checkout_url(pkgdirurl)
svn.copy(currenturl, pristineurl,
log="Copying release %s-%s to pristine/ directory." %
(version, release))
markreleaselog = create_markrelease_log(version, release, revision)
- svn.copy(currenturl, releaseurl, revision=revision,
+ svn.copy(currenturl, releaseurl, rev=revision,
log=markreleaselog)
def check_changed(pkgdirurl, all=0, show=0, verbose=0):
@@ -305,29 +374,30 @@ def check_changed(pkgdirurl, all=0, show=0, verbose=0):
if verbose:
print "Getting list of packages...",
sys.stdout.flush()
- packages = [x['name'] for x in svn.ls(baseurl)]
+ packages = [x[:-1] for x in svn.ls(baseurl)]
if verbose:
print "done"
if not packages:
raise Error, "couldn't get list of packages"
else:
- packages = [pkgdirurl]
+ baseurl, basename = os.path.split(pkgdirurl)
+ packages = [basename]
clean = []
changed = []
nopristine = []
nocurrent = []
- for pkgdirurl in packages:
- package = os.path.basename(pkgdirurl)
- current = os.path.join(pkgdirurl, "current")
- pristine = os.path.join(pkgdirurl, "pristine")
+ for package in packages:
+ pkgdirurl = os.path.join(baseurl, package)
+ current = layout.checkout_url(pkgdirurl)
+ pristine = layout.checkout_url(pkgdirurl, pristine=True)
if verbose:
print "Checking package %s..." % package,
sys.stdout.flush()
- if not svn.exists(current):
+ if not svn.ls(current, noerror=1):
if verbose:
print "NO CURRENT"
nocurrent.append(package)
- elif not svn.exists(pristine):
+ elif not svn.ls(pristine, noerror=1):
if verbose:
print "NO PRISTINE"
nopristine.append(package)
@@ -356,19 +426,16 @@ def check_changed(pkgdirurl, all=0, show=0, verbose=0):
"nocurrent": nocurrent,
"nopristine": nopristine}
-def checkout(pkgdirurl, path=None, revision=None, use_mirror=True):
+def checkout(pkgdirurl, path=None, revision=None, branch=None,
+ distro=None):
o_pkgdirurl = pkgdirurl
- pkgdirurl = default_parent(o_pkgdirurl)
- current = os.path.join(pkgdirurl, "current")
+ pkgdirurl = layout.package_url(o_pkgdirurl, distro=distro)
+ current = layout.checkout_url(pkgdirurl, branch=branch)
if path is None:
- _, path = os.path.split(pkgdirurl)
- # if default_parent changed the URL, we can use mirrors because the
- # user did not provided complete package URL
- if (o_pkgdirurl != pkgdirurl) and use_mirror and mirror.enabled():
- current = mirror.checkout_url(current)
- print "checking out from mirror", current
+ path = layout.package_name(pkgdirurl)
+ mirror.info(current)
svn = SVN()
- svn.checkout(current, path, revision=SVN.makerev(revision), show=1)
+ svn.checkout(current, path, rev=revision, show=1)
def _getpkgtopdir(basedir=None):
if basedir is None:
@@ -378,15 +445,15 @@ def _getpkgtopdir(basedir=None):
if dirname == "SPECS" or dirname == "SOURCES":
topdir = os.pardir
else:
- topdir = "."
+ topdir = ""
return topdir
-def sync(dryrun=False):
+def sync(dryrun=False, download=False):
svn = SVN()
topdir = _getpkgtopdir()
# run svn info because svn st does not complain when topdir is not an
# working copy
- svn.info(topdir)
+ svn.info(topdir or ".")
specsdir = os.path.join(topdir, "SPECS/")
sourcesdir = os.path.join(topdir, "SOURCES/")
for path in (specsdir, sourcesdir):
@@ -401,18 +468,34 @@ def sync(dryrun=False):
spec = rpm.TransactionSet().parseSpec(specpath)
except rpm.error, e:
raise Error, "could not load spec file: %s" % e
- sources = [os.path.basename(name)
- for name, no, flags in spec.sources()]
- sourcesst = dict((os.path.basename(st.path), st)
- for st in svn.status(sourcesdir, get_all=False, ignore=True))
+ sources = dict((os.path.basename(name), name)
+ for name, no, flags in spec.sources())
+ sourcesst = dict((os.path.basename(path), (path, st))
+ for st, path in svn.status(sourcesdir, noignore=True))
toadd = []
- for source in sources:
+ for source, url in sources.iteritems():
sourcepath = os.path.join(sourcesdir, source)
- if sourcesst.get(source):
+ pst = sourcesst.get(source)
+ if pst:
+ if os.path.isfile(sourcepath):
+ toadd.append(sourcepath)
+ else:
+ sys.stderr.write("warning: %s not found, skipping\n" % sourcepath)
+ elif download and not os.path.isfile(sourcepath):
+ print "%s not found, downloading from %s" % (sourcepath, url)
+ fmt = config.get("global", "download-command",
+ "wget -c -O '$dest' $url")
+ context = {"dest": sourcepath, "url": url}
+ try:
+ cmd = string.Template(fmt).substitute(context)
+ except KeyError, e:
+ raise Error, "invalid variable %r in download-command "\
+ "configuration option" % e
+ execcmd(cmd, show=True)
if os.path.isfile(sourcepath):
toadd.append(sourcepath)
else:
- sys.stderr.write("warning: %s not found\n" % sourcepath)
+ raise Error, "file not found: %s" % sourcepath
# rm entries not found in sources and still in svn
found = os.listdir(sourcesdir)
toremove = []
@@ -426,31 +509,35 @@ def sync(dryrun=False):
for path in toremove:
print "D\t%s" % path
if not dryrun:
- svn.remove(path)
+ svn.remove(path, local=True)
for path in toadd:
print "A\t%s" % path
if not dryrun:
- svn.add(path)
+ svn.add(path, local=True)
-def commit(target=".", message=None):
+def commit(target=".", message=None, logfile=None):
svn = SVN()
- status = svn.status(target, silent=True)
+ status = svn.status(target, quiet=True)
if not status:
print "nothing to commit"
return
- info = svn.info(target)
- url = info.url
+ info = svn.info2(target)
+ url = info.get("URL")
if url is None:
raise Error, "working copy URL not provided by svn info"
- mirrored = mirror.enabled(url)
+ mirrored = mirror.using_on(url)
if mirrored:
newurl = mirror.switchto_parent(svn, url, target)
print "relocated to", newurl
- # we can't use the svn object here because pexpect hides VISUAL
- mopt = ""
+ # we can't use the svn object here because svn --non-interactive option
+ # hides VISUAL
+ opts = []
if message is not None:
- mopt = "-m \"%s\"" % message
- os.system("svn ci %s %s" % (mopt, target))
+ opts.append("-m \"%s\"" % message)
+ if logfile is not None:
+ opts.append("-F \"%s\"" % logfile)
+ mopts = " ".join(opts)
+ os.system("svn ci %s %s" % (mopts, target))
if mirrored:
print "use \"repsys switch\" in order to switch back to mirror "\
"later"
@@ -458,8 +545,10 @@ def commit(target=".", message=None):
def switch(mirrorurl=None):
svn = SVN()
topdir = _getpkgtopdir()
- info = svn.info(topdir)
- wcurl = info.url
+ info = svn.info2(topdir)
+ wcurl = info.get("URL")
+ if wcurl is None:
+ raise Error, "working copy URL not provided by svn info"
newurl = mirror.autoswitch(svn, topdir, wcurl, mirrorurl)
print "switched to", newurl
@@ -483,31 +572,43 @@ def get_submit_info(path):
if not os.path.isdir(os.path.join(path, ".svn")):
raise Error, "subversion directory not found"
- # Now, extract the package name.
svn = SVN()
- info = svn.info(path)
- url = info.url
+ # Now, extract the package name.
+ info = svn.info2(path)
+ url = info.get("URL")
+ if url is None:
+ raise Error, "missing URL from svn info %s" % path
toks = url.split("/")
if len(toks) < 2 or toks[-1] != "current":
- raise Error, "invalid package URL %s, needs current/" % url
+ raise Error, "unexpected URL received from 'svn info'"
name = toks[-2]
+ url = "/".join(toks[:-1])
# Finally, guess revision.
max = -1
files = []
files.extend(glob.glob("%s/*" % specsdir))
files.extend(glob.glob("%s/*" % sourcesdir))
- for file in files:
- info = svn.info(file)
- if info is None: # not in working copy
+ for file in files:
+ try:
+ info = svn.info2(file)
+ except Error:
+ # possibly not tracked
continue
- rev = info.commit_revision.number
- if rev > max:
- max = rev
+ if info is None:
+ continue
+ rawrev = info.get("Last Changed Rev")
+ if rawrev:
+ rev = int(rawrev)
+ if rev > max:
+ max = rev
if max == -1:
- raise Error, "unable to find the latest revision"
+ raise Error, "revision tag not found in 'svn info' output"
+
+ if mirror.using_on(url):
+ url = mirror.switchto_parent_url(url)
- return name, max
+ return name, url, max
# vim:et:ts=4:sw=4
diff --git a/RepSys/svn.py b/RepSys/svn.py
index 527b1e5..985329d 100644
--- a/RepSys/svn.py
+++ b/RepSys/svn.py
@@ -1,18 +1,10 @@
from RepSys import Error, config
from RepSys.util import execcmd, get_auth
-
import sys
-import os
import re
import time
-import threading
-import tempfile
-import pysvn
-
-__all__ = ["SVN", "Revision", "SVNLogEntry", "SVNError"]
-class SVNError(Error):
- pass
+__all__ = ["SVN", "SVNLook", "SVNLogEntry"]
class SVNLogEntry:
def __init__(self, revision, author, date):
@@ -26,208 +18,347 @@ class SVNLogEntry:
return cmp(self.date, other.date)
class SVN:
- _client = None
- _client_lock = None
- _current_message = None
-
- def __init__(self):
- self._client = pysvn.Client()
- self._client_lock = threading.Lock()
- self._client.callback_get_log_message = self._log_handler
- self._client.callback_get_login = self._unsupported_auth
- self._client.callback_ssl_client_cert_password_prompt = \
- self._unsupported_auth
- self._client.callback_ssl_client_cert_prompt = \
- self._unsupported_auth
- self._client.callback_ssl_server_prompt = \
- self._unsupported_auth
-
- def _log_handler(self):
- if self._current_message is None:
- #TODO make it use EDITOR
- raise ValueError, "No log message defined"
- return True, self._current_message
-
- def _unsupported_auth(self, *args, **kwargs):
- raise SVNError, "svn is trying to get login information, " \
- "seems that you're not using ssh-agent"
-
- def _get_log_message(self, received_kwargs):
- message = received_kwargs.pop("log", None)
- messagefile = received_kwargs.pop("logfile", None)
- if messagefile and not message:
- message = open(messagefile).read()
- return message
-
- def _set_notify_callback(self, callback):
- self._client.callback_notify = callback
-
- def _make_wrapper(self, meth, notify=None):
- def wrapper(*args, **kwargs):
- self._client_lock.acquire()
- try:
- self._current_message = self._get_log_message(kwargs)
- ignore_errors = kwargs.pop("noerror", None)
- if notify:
- self._client.callback_notify = notify
- try:
- return meth(*args, **kwargs)
- except pysvn.ClientError, (msg,):
- if not ignore_errors:
- raise SVNError, msg
- return None
- finally:
- self._client_lock.release()
- self._current_message = None
- return wrapper
-
- def _client_wrap(self, attrname):
- meth = getattr(self._client, attrname)
- wrapper = self._make_wrapper(meth)
- return wrapper
-
- def __getattr__(self, attrname):
- return self._client_wrap(attrname)
-
- def makerev(number=None, head=None):
- if number is not None:
- args = (pysvn.opt_revision_kind.number, number)
- else:
- args = (pysvn.opt_revision_kind.head,)
- return pysvn.Revision(*args)
- makerev = staticmethod(makerev)
-
- def revision(self, url, last_changed=False):
- infos = self._client.info2(url, recurse=False)
- if last_changed:
- revnum = infos[0][1].last_changed_rev.number
- else:
- revnum = infos[0][1].rev.number
- return revnum
-
- # this override method fixed the problem in pysvn's mkdir which
- # requires a log_message parameter
- def mkdir(self, path, log=None, **kwargs):
- meth = self._client_wrap("mkdir")
- # we can't raise an error because pysvn's mkdir will use
- # log_message only if path is remote, but it *always* requires this
- # parameter. Also, 'log' is never used.
- log = log or "There's a silent bug in your code"
- return meth(path, log, log=None, **kwargs)
-
- def checkout(self, url, targetpath, show=False, **kwargs):
- if show:
- def callback(event):
- types = pysvn.wc_notify_action
- action = event["action"]
- if action == types.update_add:
- print "A %s" % event["path"]
- elif action == types.update_completed:
- print "Checked out revision %d" % \
- event["revision"].number
- self._set_notify_callback(callback)
- meth = self._client_wrap("checkout")
- meth(url, targetpath, **kwargs)
-
- def checkin(self, path, log, **kwargs):
- # XXX use EDITOR when log empty
- meth = self._client_wrap("checkin")
- return meth(path, log, log=None, **kwargs)
-
- def log(self, *args, **kwargs):
- meth = self._client_wrap("log")
- entries = meth(discover_changed_paths=True, *args, **kwargs)
- if entries is None:
- return
- for entrydic in entries:
- entry = SVNLogEntry(entrydic["revision"].number,
- entrydic["author"],
- time.localtime(entrydic["date"]))
- entry.lines[:] = entrydic["message"].split("\n")
- for cp in entrydic["changed_paths"]:
- from_rev = cp["copyfrom_revision"]
- if from_rev:
- from_rev = from_rev.number
- changed = {
- "action": cp["action"],
- "path": cp["path"],
- "from_rev": from_rev,
- "from_path": cp["copyfrom_path"],
- }
- entry.changed.append(changed)
- yield entry
-
- def exists(self, path):
- return self.ls(path, noerror=1) is not None
-
- def status(self, *args, **kwargs):
- # add one keywork "silent" that workaround the strange behavior of
- # pysvn's get_all, which seems to be broken, this way we also have
- # the same interface of svn.py from repsys 1.6.x
- meth = self._client_wrap("status")
- silent = kwargs.pop("silent", None)
- st = meth(*args, **kwargs)
- if silent:
- unversioned = pysvn.wc_status_kind.unversioned
- st = [entry for entry in st
- if entry.text_status is not unversioned]
- return st
-
- def diff(self, path1, *args, **kwargs):
- head = pysvn.Revision(pysvn.opt_revision_kind.head)
- revision1 = kwargs.pop("revision1", head)
- revision2 = kwargs.pop("revision2", head)
- if args:
- kwargs["url_or_path2"] = args[0]
- tmpdir = tempfile.gettempdir()
- meth = self._client_wrap("diff")
- diff_text = meth(tmpdir, path1, revision1=revision1,
- revision2=revision2, **kwargs)
- return diff_text
-
- def _edit_message(self, message):
- # argh!
- editor = os.getenv("EDITOR", "vim")
- fd, fpath = tempfile.mkstemp(prefix="repsys")
- result = (False, None)
+ def _execsvn(self, *args, **kwargs):
+ localcmds = ("add", "revert", "cleanup")
+ if not kwargs.get("show") and args[0] not in localcmds:
+ args = list(args)
+ args.append("--non-interactive")
+ svn_command = config.get("global", "svn-command",
+ "SVN_SSH='ssh -o \"BatchMode yes\"' svn")
+ cmdstr = svn_command + " " + " ".join(args)
try:
- f = os.fdopen(fd, "w")
- f.write(message)
- f.close()
- lastchange = os.stat(fpath).st_mtime
- for i in xrange(10):
- status = os.system("%s %s" % (editor, fpath))
- if status != 0:
- raise SVNError, "the editor failed with %d" % status
- newchange = os.stat(fpath).st_mtime
- if newchange == lastchange:
- print "Log message unchanged or not specified"
- print "(a)bort, (c)ontinue, (e)dit"
- choice = raw_input()
- if not choice or choice[0] == 'e':
- continue
- elif choice[0] == 'a':
- break
- elif choice[0] == 'c':
- pass # ignore and go ahead
- newmessage = open(fpath).read()
- result = (True, newmessage)
- break
- finally:
- os.unlink(fpath)
- return result
-
- def propedit(self, propname, pkgdirurl, revision, revprop=False):
- revision = (revision)
- if revprop:
- propget = self.revpropget
- propset = self.revpropset
+ return execcmd(cmdstr, **kwargs)
+ except Error, e:
+ if "Permission denied" in e.message:
+ raise Error, ("%s\n"
+ "Seems ssh-agent or ForwardAgent are not setup, see "
+ "http://wiki.mandriva.com/en/Development/Docs/Contributor_Tricks#SSH_configuration"
+ " for more information." % e)
+ elif "authorization failed" in e.message:
+ raise Error, ("%s\n"
+ "Note that repsys does not support any HTTP "
+ "authenticated access." % e)
+ raise
+
+ def _execsvn_success(self, *args, **kwargs):
+ status, output = self._execsvn(*args, **kwargs)
+ return status == 0
+
+ def _add_log(self, cmd_args, received_kwargs, optional=0):
+ if (not optional or
+ received_kwargs.has_key("log") or
+ received_kwargs.has_key("logfile")):
+ ret = received_kwargs.get("log")
+ if ret is not None:
+ cmd_args.append("-m '%s'" % ret)
+ ret = received_kwargs.get("logfile")
+ if ret is not None:
+ cmd_args.append("-F '%s'" % ret)
+
+ def _add_revision(self, cmd_args, received_kwargs, optional=0):
+ if not optional or received_kwargs.has_key("rev"):
+ ret = received_kwargs.get("rev")
+ if isinstance(ret, basestring):
+ try:
+ ret = int(ret)
+ except ValueError:
+ raise Error, "invalid revision provided"
+ if ret:
+ cmd_args.append("-r %d" % ret)
+
+ def add(self, path, **kwargs):
+ cmd = ["add", path]
+ return self._execsvn_success(noauth=1, *cmd, **kwargs)
+
+ def copy(self, pathfrom, pathto, **kwargs):
+ cmd = ["copy", pathfrom, pathto]
+ self._add_revision(cmd, kwargs, optional=1)
+ self._add_log(cmd, kwargs)
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def remove(self, path, force=0, **kwargs):
+ cmd = ["remove", path]
+ self._add_log(cmd, kwargs)
+ if force:
+ cmd.append("--force")
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def mkdir(self, path, **kwargs):
+ cmd = ["mkdir", path]
+ self._add_log(cmd, kwargs)
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def commit(self, path, **kwargs):
+ cmd = ["commit", path]
+ self._add_log(cmd, kwargs)
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def export(self, url, targetpath, **kwargs):
+ cmd = ["export", "'%s'" % url, targetpath]
+ self._add_revision(cmd, kwargs, optional=1)
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def checkout(self, url, targetpath, **kwargs):
+ cmd = ["checkout", "'%s'" % url, targetpath]
+ self._add_revision(cmd, kwargs, optional=1)
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def propset(self, propname, value, targets, **kwargs):
+ cmd = ["propset", propname, "'%s'" % value, targets]
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def propedit(self, propname, target, **kwargs):
+ cmd = ["propedit", propname, target]
+ if kwargs.get("rev"):
+ cmd.append("--revprop")
+ self._add_revision(cmd, kwargs)
+ return self._execsvn_success(local=True, show=True, *cmd, **kwargs)
+
+ def revision(self, path, **kwargs):
+ cmd = ["info", path]
+ status, output = self._execsvn(local=True, *cmd, **kwargs)
+ if status == 0:
+ for line in output.splitlines():
+ if line.startswith("Last Changed Rev: "):
+ return int(line.split()[3])
+ return None
+
+ def info(self, path, **kwargs):
+ cmd = ["info", path]
+ status, output = self._execsvn(local=True, *cmd, **kwargs)
+ if status == 0 and "Not a versioned resource" not in output:
+ return output.splitlines()
+ return None
+
+ def info2(self, *args, **kwargs):
+ lines = self.info(*args, **kwargs)
+ if lines is None:
+ return None
+ pairs = [[w.strip() for w in line.split(":", 1)] for line in lines]
+ info = dict(pairs)
+ return info
+
+ def ls(self, path, **kwargs):
+ cmd = ["ls", path]
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return output.split()
+ return None
+
+ def status(self, path, **kwargs):
+ cmd = ["status", path]
+ if kwargs.get("verbose"):
+ cmd.append("-v")
+ if kwargs.get("noignore"):
+ cmd.append("--no-ignore")
+ if kwargs.get("quiet"):
+ cmd.append("--quiet")
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return [x.split() for x in output.splitlines()]
+ return None
+
+ def cleanup(self, path, **kwargs):
+ cmd = ["cleanup", path]
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def revert(self, path, **kwargs):
+ cmd = ["revert", path]
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return [x.split() for x in output.split()]
+ return None
+
+ def switch(self, url, oldurl=None, path=None, relocate=False, **kwargs):
+ cmd = ["switch"]
+ if relocate:
+ if oldurl is None:
+ raise Error, "You must supply the old URL when "\
+ "relocating working copies"
+ cmd.append("--relocate")
+ cmd.append(oldurl)
+ cmd.append(url)
+ if path is not None:
+ cmd.append(path)
+ return self._execsvn_success(*cmd, **kwargs)
+
+ def update(self, path, **kwargs):
+ cmd = ["update", path]
+ self._add_revision(cmd, kwargs, optional=1)
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return [x.split() for x in output.split()]
+ return None
+
+ def merge(self, url1, url2=None, rev1=None, rev2=None, path=None,
+ **kwargs):
+ cmd = ["merge"]
+ if rev1 and rev2 and not url2:
+ cmd.append("-r")
+ cmd.append("%s:%s" % (rev1, rev2))
+ cmd.append(url1)
else:
- propget = self.propget
- propset = self.propset
- revision, message = propget(propname, pkgdirurl, revision=revision)
- changed, newmessage = self._edit_message(message)
- if changed:
- propset(propname, newmessage, pkgdirurl, revision=revision)
+ if not url2:
+ raise ValueError, \
+ "url2 needed if two revisions are not provided"
+ if rev1:
+ cmd.append("%s@%s" % (url1, rev1))
+ else:
+ cmd.append(url1)
+ if rev2:
+ cmd.append("%s@%s" % (url2, rev2))
+ else:
+ cmd.append(url2)
+ if path:
+ cmd.append(path)
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return [x.split() for x in output.split()]
+ return None
+
+ def diff(self, pathurl1, pathurl2=None, **kwargs):
+ cmd = ["diff", pathurl1]
+ self._add_revision(cmd, kwargs, optional=1)
+ if pathurl2:
+ cmd.append(pathurl2)
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return output
+ return None
+
+ def cat(self, url, **kwargs):
+ cmd = ["cat", url]
+ self._add_revision(cmd, kwargs, optional=1)
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status == 0:
+ return output
+ return None
+
+ def log(self, url, start=None, end=0, limit=None, **kwargs):
+ cmd = ["log", "-v", url]
+ if start is not None or end != 0:
+ if start is not None and type(start) is not type(0):
+ try:
+ start = int(start)
+ except (ValueError, TypeError):
+ raise Error, "invalid log start revision provided"
+ if type(end) is not type(0):
+ try:
+ end = int(end)
+ except (ValueError, TypeError):
+ raise Error, "invalid log end revision provided"
+ start = start or "HEAD"
+ cmd.append("-r %s:%s" % (start, end))
+ if limit is not None:
+ try:
+ limit = int(limit)
+ except (ValueError, TypeError):
+ raise Error, "invalid limit number provided"
+ cmd.append("--limit %d" % limit)
+ status, output = self._execsvn(*cmd, **kwargs)
+ if status != 0:
+ return None
+
+ revheader = re.compile("^r(?P<revision>[0-9]+) \| (?P<author>[^\|]+) \| (?P<date>[^\|]+) \| (?P<lines>[0-9]+) (?:line|lines)$")
+ changedpat = re.compile(r"^\s+(?P<action>[^\s]+) (?P<path>[^\s]+)(?: \([^\s]+ (?P<from_path>[^:]+)(?:\:(?P<from_rev>[0-9]+))?\))?$")
+ logseparator = "-"*72
+ linesleft = 0
+ entry = None
+ log = []
+ appendchanged = 0
+ changedheader = 0
+ for line in output.splitlines():
+ line = line.rstrip()
+ if changedheader:
+ appendchanged = 1
+ changedheader = 0
+ elif appendchanged:
+ if not line:
+ appendchanged = 0
+ continue
+ m = changedpat.match(line)
+ if m:
+ changed = m.groupdict().copy()
+ from_rev = changed.get("from_rev")
+ if from_rev is not None:
+ try:
+ changed["from_rev"] = int(from_rev)
+ except (ValueError, TypeError):
+ raise Error, "invalid revision number in svn log"
+ entry.changed.append(changed)
+ elif linesleft == 0:
+ if line != logseparator:
+ m = revheader.match(line)
+ if m:
+ linesleft = int(m.group("lines"))
+ timestr = " ".join(m.group("date").split()[:2])
+ timetuple = time.strptime(timestr,
+ "%Y-%m-%d %H:%M:%S")
+ entry = SVNLogEntry(int(m.group("revision")),
+ m.group("author"), timetuple)
+ log.append(entry)
+ changedheader = 1
+ else:
+ entry.lines.append(line)
+ linesleft -= 1
+ log.sort()
+ log.reverse()
+ return log
+
+class SVNLook:
+ def __init__(self, repospath, txn=None, rev=None):
+ self.repospath = repospath
+ self.txn = txn
+ self.rev = rev
+
+ def _execsvnlook(self, cmd, *args, **kwargs):
+ execcmd_args = ["svnlook", cmd, self.repospath]
+ self._add_txnrev(execcmd_args, kwargs)
+ execcmd_args += args
+ execcmd_kwargs = {}
+ keywords = ["show", "noerror"]
+ for key in keywords:
+ if kwargs.has_key(key):
+ execcmd_kwargs[key] = kwargs[key]
+ return execcmd(*execcmd_args, **execcmd_kwargs)
+
+ def _add_txnrev(self, cmd_args, received_kwargs):
+ if received_kwargs.has_key("txn"):
+ txn = received_kwargs.get("txn")
+ if txn is not None:
+ cmd_args += ["-t", txn]
+ elif self.txn is not None:
+ cmd_args += ["-t", self.txn]
+ if received_kwargs.has_key("rev"):
+ rev = received_kwargs.get("rev")
+ if rev is not None:
+ cmd_args += ["-r", rev]
+ elif self.rev is not None:
+ cmd_args += ["-r", self.rev]
+
+ def changed(self, **kwargs):
+ status, output = self._execsvnlook("changed", **kwargs)
+ if status != 0:
+ return None
+ changes = []
+ for line in output.splitlines():
+ line = line.rstrip()
+ if not line:
+ continue
+ entry = [None, None, None]
+ changedata, changeprop, path = None, None, None
+ if line[0] != "_":
+ changedata = line[0]
+ if line[1] != " ":
+ changeprop = line[1]
+ path = line[4:]
+ changes.append((changedata, changeprop, path))
+ return changes
+
+ def author(self, **kwargs):
+ status, output = self._execsvnlook("author", **kwargs)
+ if status != 0:
+ return None
+ return output.strip()
# vim:et:ts=4:sw=4
diff --git a/RepSys/util.py b/RepSys/util.py
index 8c66199..83f2ebe 100644
--- a/RepSys/util.py
+++ b/RepSys/util.py
@@ -29,7 +29,8 @@ def execcmd(*cmd, **kwargs):
status = os.system(cmdstr)
output = ""
else:
- status, output = commands_getstatusoutput("LANG=C LANGUAGE=C LC_ALL=C "+cmdstr)
+ status, output = commands_getstatusoutput(
+ "LANG=C LANGUAGE=C LC_ALL=C "+cmdstr)
if status != 0 and not kwargs.get("noerror"):
raise Error, "command failed: %s\n%s\n" % (cmdstr, output)
if config.getbool("global", "verbose", 0):