import_srpm.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. # vim: set fileencoding=utf-8 :
  2. #
  3. # (C) 2006,2007,2011 Guido Guenther <agx@sigxcpu.org>
  4. # (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, please see
  17. # <http://www.gnu.org/licenses/>
  18. """Import an RPM source package into a Git repository"""
  19. from six.moves import configparser
  20. import sys
  21. import re
  22. import os
  23. import glob
  24. import time
  25. import shutil
  26. import errno
  27. from six.moves.urllib.request import urlopen
  28. from six.moves import urllib
  29. import gbp.command_wrappers as gbpc
  30. from gbp.tmpfile import init_tmpdir, del_tmpdir, tempfile
  31. from gbp.rpm import (parse_srpm, guess_spec, SpecFile, NoSpecError,
  32. RpmUpstreamSource, compose_version_str)
  33. from gbp.rpm.git import (RpmGitRepository, GitRepositoryError)
  34. from gbp.git.modifier import GitModifier
  35. from gbp.config import (GbpOptionParserRpm, GbpOptionGroup,
  36. no_upstream_branch_msg)
  37. from gbp.errors import GbpError
  38. import gbp.log
  39. from gbp.pkg import parse_archive_filename
  40. no_packaging_branch_msg = """
  41. Repository does not have branch '%s' for packaging/distribution sources.
  42. You need to reate it or use --packaging-branch to specify it.
  43. """
  44. class SkipImport(Exception):
  45. """Nothing imported"""
  46. pass
  47. def download_file(target_dir, url):
  48. """Download a remote file"""
  49. gbp.log.info("Downloading '%s'..." % url)
  50. try:
  51. urlobj = urlopen(url)
  52. local_fn = os.path.join(target_dir, os.path.basename(url))
  53. with open(local_fn, "wb") as local_file:
  54. local_file.write(urlobj.read())
  55. except urllib.error.HTTPError as err:
  56. raise GbpError("Download failed: %s" % err)
  57. except urllib.error.URLError as err:
  58. raise GbpError("Download failed: %s" % err.reason)
  59. return local_fn
  60. def download_source(pkg):
  61. """Download package from a remote location"""
  62. if re.match(r'[a-z]{1,5}://', pkg):
  63. mode = 'python urllib'
  64. else:
  65. mode = 'yumdownloader'
  66. tmpdir = tempfile.mkdtemp(prefix='download_')
  67. gbp.log.info("Trying to download '%s' using '%s'..." % (pkg, mode))
  68. if mode == 'yumdownloader':
  69. gbpc.RunAtCommand('yumdownloader',
  70. ['--source', '--destdir=', '.', pkg],
  71. shell=False)(dir=tmpdir)
  72. else:
  73. download_file(tmpdir, pkg)
  74. srpm = glob.glob(os.path.join(tmpdir, '*.src.rpm'))[0]
  75. return srpm
  76. def committer_from_author(author, options):
  77. """Get committer info based on options"""
  78. committer = GitModifier()
  79. if options.author_is_committer:
  80. committer.name = author.name
  81. committer.email = author.email
  82. return committer
  83. def move_tag_stamp(repo, tag_format, tag_str_fields):
  84. "Move tag out of the way appending the current timestamp"
  85. old = repo.version_to_tag(tag_format, tag_str_fields)
  86. new = repo.version_to_tag('%s~%d' % (tag_format, int(time.time())),
  87. tag_str_fields)
  88. repo.move_tag(old, new)
  89. def set_bare_repo_options(options):
  90. """Modify options for import into a bare repository"""
  91. if options.pristine_tar:
  92. gbp.log.info("Bare repository: setting %s option '--no-pristine-tar'")
  93. options.pristine_tar = False
  94. def force_to_branch_head(repo, branch):
  95. """Checkout branch and reset --hard"""
  96. if repo.get_branch() == branch:
  97. # Update HEAD if we modified the checked out branch
  98. repo.force_head(branch, hard=True)
  99. # Checkout packaging branch
  100. repo.set_branch(branch)
  101. def build_parser(name):
  102. """Construct command line parser"""
  103. try:
  104. parser = GbpOptionParserRpm(command=os.path.basename(name),
  105. prefix='',
  106. usage='%prog [options] /path/to/package'
  107. '.src.rpm')
  108. except configparser.ParsingError as err:
  109. gbp.log.err(err)
  110. return None
  111. import_group = GbpOptionGroup(parser, "import options",
  112. "pristine-tar and filtering")
  113. tag_group = GbpOptionGroup(parser, "tag options",
  114. "options related to git tag creation")
  115. branch_group = GbpOptionGroup(parser, "version and branch naming options",
  116. "version number and branch layout options")
  117. for group in [import_group, branch_group, tag_group ]:
  118. parser.add_option_group(group)
  119. parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
  120. default=False, help="verbose command execution")
  121. parser.add_config_file_option(option_name="color", dest="color",
  122. type='tristate')
  123. parser.add_config_file_option(option_name="color-scheme",
  124. dest="color_scheme")
  125. parser.add_config_file_option(option_name="tmp-dir", dest="tmp_dir")
  126. parser.add_config_file_option(option_name="vendor", action="store",
  127. dest="vendor")
  128. parser.add_option("--download", action="store_true", dest="download",
  129. default=False, help="download source package")
  130. branch_group.add_config_file_option(option_name="packaging-branch",
  131. dest="packaging_branch")
  132. branch_group.add_config_file_option(option_name="upstream-branch",
  133. dest="upstream_branch")
  134. branch_group.add_boolean_config_file_option(
  135. option_name="create-missing-branches",
  136. dest="create_missing_branches")
  137. branch_group.add_option("--orphan-packaging", action="store_true",
  138. dest="orphan_packaging", default=False,
  139. help="The packaging branch doesn't base on upstream")
  140. branch_group.add_option("--native", action="store_true",
  141. dest="native", default=False,
  142. help="This is a dist native package, no separate "
  143. "upstream branch")
  144. tag_group.add_boolean_config_file_option(option_name="sign-tags",
  145. dest="sign_tags")
  146. tag_group.add_config_file_option(option_name="keyid",
  147. dest="keyid")
  148. tag_group.add_config_file_option(option_name="packaging-tag",
  149. dest="packaging_tag")
  150. tag_group.add_config_file_option(option_name="upstream-tag",
  151. dest="upstream_tag")
  152. import_group.add_config_file_option(option_name="filter",
  153. dest="filters", action="append")
  154. import_group.add_boolean_config_file_option(option_name="pristine-tar",
  155. dest="pristine_tar")
  156. import_group.add_option("--allow-same-version", action="store_true",
  157. dest="allow_same_version", default=False,
  158. help="allow to import already imported version")
  159. import_group.add_boolean_config_file_option(
  160. option_name="author-is-committer",
  161. dest="author_is_committer")
  162. import_group.add_config_file_option(option_name="packaging-dir",
  163. dest="packaging_dir")
  164. return parser
  165. def parse_args(argv):
  166. """Parse commandline arguments"""
  167. parser = build_parser(argv[0])
  168. if not parser:
  169. return None, None
  170. (options, args) = parser.parse_args(argv[1:])
  171. gbp.log.setup(options.color, options.verbose, options.color_scheme)
  172. return options, args
  173. def main(argv):
  174. """Main function of the git-import-srpm script"""
  175. dirs = dict(top=os.path.abspath(os.curdir))
  176. ret = 0
  177. skipped = False
  178. options, args = parse_args(argv)
  179. if len(args) != 1:
  180. gbp.log.err("Need to give exactly one package to import. Try --help.")
  181. return 1
  182. try:
  183. dirs['tmp_base'] = init_tmpdir(options.tmp_dir, 'import-srpm_')
  184. except GbpError as err:
  185. gbp.log.err(err)
  186. return 1
  187. try:
  188. srpm = args[0]
  189. if options.download:
  190. srpm = download_source(srpm)
  191. # Real srpm, we need to unpack, first
  192. true_srcrpm = False
  193. if not os.path.isdir(srpm) and not srpm.endswith(".spec"):
  194. src = parse_srpm(srpm)
  195. true_srcrpm = True
  196. dirs['pkgextract'] = tempfile.mkdtemp(prefix='pkgextract_')
  197. gbp.log.info("Extracting src rpm to '%s'" % dirs['pkgextract'])
  198. src.unpack(dirs['pkgextract'])
  199. preferred_spec = src.name + '.spec'
  200. srpm = dirs['pkgextract']
  201. elif os.path.isdir(srpm):
  202. preferred_spec = os.path.basename(srpm.rstrip('/')) + '.spec'
  203. else:
  204. preferred_spec = None
  205. # Find and parse spec file
  206. if os.path.isdir(srpm):
  207. gbp.log.debug("Trying to import an unpacked srpm from '%s'" % srpm)
  208. dirs['src'] = os.path.abspath(srpm)
  209. spec = guess_spec(srpm, True, preferred_spec)
  210. else:
  211. gbp.log.debug("Trying to import an srpm from '%s' with spec "\
  212. "file '%s'" % (os.path.dirname(srpm), srpm))
  213. dirs['src'] = os.path.abspath(os.path.dirname(srpm))
  214. spec = SpecFile(srpm)
  215. # Check the repository state
  216. try:
  217. repo = RpmGitRepository('.')
  218. is_empty = repo.is_empty()
  219. (clean, out) = repo.is_clean()
  220. if not clean and not is_empty:
  221. gbp.log.err("Repository has uncommitted changes, commit "
  222. "these first: ")
  223. raise GbpError(out)
  224. except GitRepositoryError:
  225. gbp.log.info("No git repository found, creating one.")
  226. is_empty = True
  227. repo = RpmGitRepository.create(spec.name)
  228. os.chdir(repo.path)
  229. if repo.bare:
  230. set_bare_repo_options(options)
  231. # Create more tempdirs
  232. dirs['origsrc'] = tempfile.mkdtemp(prefix='origsrc_')
  233. dirs['packaging_base'] = tempfile.mkdtemp(prefix='packaging_')
  234. dirs['packaging'] = os.path.join(dirs['packaging_base'],
  235. options.packaging_dir)
  236. try:
  237. os.mkdir(dirs['packaging'])
  238. except OSError as err:
  239. if err.errno != errno.EEXIST:
  240. raise
  241. if true_srcrpm:
  242. # For true src.rpm we just take everything
  243. files = os.listdir(dirs['src'])
  244. else:
  245. # Need to copy files to the packaging directory given by caller
  246. files = [os.path.basename(patch.path) \
  247. for patch in spec.patchseries(unapplied=True, ignored=True)]
  248. for filename in spec.sources().values():
  249. files.append(os.path.basename(filename))
  250. files.append(os.path.join(spec.specdir, spec.specfile))
  251. # Don't copy orig source archive, though
  252. if spec.orig_src and spec.orig_src['filename'] in files:
  253. files.remove(spec.orig_src['filename'])
  254. for fname in files:
  255. fpath = os.path.join(dirs['src'], fname)
  256. if os.path.exists(fpath):
  257. shutil.copy2(fpath, dirs['packaging'])
  258. else:
  259. gbp.log.err("File '%s' listed in spec not found" % fname)
  260. raise GbpError
  261. # Unpack orig source archive
  262. if spec.orig_src:
  263. orig_tarball = os.path.join(dirs['src'], spec.orig_src['filename'])
  264. sources = RpmUpstreamSource(orig_tarball)
  265. sources.unpack(dirs['origsrc'], options.filters)
  266. else:
  267. sources = None
  268. src_tag_format = options.packaging_tag if options.native \
  269. else options.upstream_tag
  270. tag_str_fields = dict(spec.version, vendor=options.vendor.lower())
  271. src_tag = repo.version_to_tag(src_tag_format, tag_str_fields)
  272. ver_str = compose_version_str(spec.version)
  273. if repo.find_version(options.packaging_tag, tag_str_fields):
  274. gbp.log.warn("Version %s already imported." % ver_str)
  275. if options.allow_same_version:
  276. gbp.log.info("Moving tag of version '%s' since import forced" %
  277. ver_str)
  278. move_tag_stamp(repo, options.packaging_tag, tag_str_fields)
  279. else:
  280. raise SkipImport
  281. if is_empty:
  282. options.create_missing_branches = True
  283. # Determine author and committer info, currently same info is used
  284. # for both sources and packaging files
  285. author = None
  286. if spec.packager:
  287. match = re.match(r'(?P<name>.*[^ ])\s*<(?P<email>\S*)>',
  288. spec.packager.strip())
  289. if match:
  290. author = GitModifier(match.group('name'), match.group('email'))
  291. if not author:
  292. author = GitModifier()
  293. gbp.log.debug("Couldn't determine packager info")
  294. committer = committer_from_author(author, options)
  295. # Import sources
  296. if sources:
  297. src_commit = repo.find_version(src_tag_format, tag_str_fields)
  298. if not src_commit:
  299. gbp.log.info("Tag %s not found, importing sources" % src_tag)
  300. branch = [options.upstream_branch,
  301. options.packaging_branch][options.native]
  302. if not repo.has_branch(branch):
  303. if options.create_missing_branches:
  304. gbp.log.info("Will create missing branch '%s'" %
  305. branch)
  306. else:
  307. gbp.log.err(no_upstream_branch_msg % branch + "\n"
  308. "Also check the --create-missing-branches option.")
  309. raise GbpError
  310. src_vendor = "Native" if options.native else "Upstream"
  311. msg = "%s version %s" % (src_vendor, spec.upstreamversion)
  312. src_commit = repo.commit_dir(sources.unpacked,
  313. "Imported %s" % msg,
  314. branch,
  315. author=author,
  316. committer=committer,
  317. create_missing_branch=options.create_missing_branches)
  318. repo.create_tag(name=src_tag,
  319. msg=msg,
  320. commit=src_commit,
  321. sign=options.sign_tags,
  322. keyid=options.keyid)
  323. if not options.native:
  324. if options.pristine_tar:
  325. archive_fmt = parse_archive_filename(orig_tarball)[1]
  326. if archive_fmt == 'tar':
  327. repo.pristine_tar.commit(orig_tarball,
  328. 'refs/heads/%s' %
  329. options.upstream_branch)
  330. else:
  331. gbp.log.warn('Ignoring pristine-tar, %s archives '
  332. 'not supported' % archive_fmt)
  333. else:
  334. gbp.log.info("No orig source archive imported")
  335. # Import packaging files. For native packages we assume that also
  336. # packaging files are found in the source tarball
  337. if not options.native or not sources:
  338. gbp.log.info("Importing packaging files")
  339. branch = options.packaging_branch
  340. if not repo.has_branch(branch):
  341. if options.create_missing_branches:
  342. gbp.log.info("Will create missing branch '%s'" % branch)
  343. else:
  344. gbp.log.err(no_packaging_branch_msg % branch + "\n"
  345. "Also check the --create-missing-branches "
  346. "option.")
  347. raise GbpError
  348. tag = repo.version_to_tag(options.packaging_tag, tag_str_fields)
  349. msg = "%s release %s" % (options.vendor, ver_str)
  350. if options.orphan_packaging or not sources:
  351. commit = repo.commit_dir(dirs['packaging_base'],
  352. "Imported %s" % msg,
  353. branch,
  354. author=author,
  355. committer=committer,
  356. create_missing_branch=options.create_missing_branches)
  357. else:
  358. # Copy packaging files to the unpacked sources dir
  359. try:
  360. pkgsubdir = os.path.join(sources.unpacked,
  361. options.packaging_dir)
  362. os.mkdir(pkgsubdir)
  363. except OSError as err:
  364. if err.errno != errno.EEXIST:
  365. raise
  366. for fname in os.listdir(dirs['packaging']):
  367. shutil.copy2(os.path.join(dirs['packaging'], fname),
  368. pkgsubdir)
  369. commit = repo.commit_dir(sources.unpacked,
  370. "Imported %s" % msg,
  371. branch,
  372. other_parents=[src_commit],
  373. author=author,
  374. committer=committer,
  375. create_missing_branch=options.create_missing_branches)
  376. # Import patches on top of the source tree
  377. # (only for non-native packages with non-orphan packaging)
  378. force_to_branch_head(repo, options.packaging_branch)
  379. # Create packaging tag
  380. repo.create_tag(name=tag,
  381. msg=msg,
  382. commit=commit,
  383. sign=options.sign_tags,
  384. keyid=options.keyid)
  385. force_to_branch_head(repo, options.packaging_branch)
  386. except KeyboardInterrupt:
  387. ret = 1
  388. gbp.log.err("Interrupted. Aborting.")
  389. except gbpc.CommandExecFailed:
  390. ret = 1
  391. except GitRepositoryError as err:
  392. gbp.log.err("Git command failed: %s" % err)
  393. ret = 1
  394. except GbpError as err:
  395. if str(err):
  396. gbp.log.err(err)
  397. ret = 1
  398. except NoSpecError as err:
  399. gbp.log.err("Failed determine spec file: %s" % err)
  400. ret = 1
  401. except SkipImport:
  402. skipped = True
  403. finally:
  404. os.chdir(dirs['top'])
  405. del_tmpdir()
  406. if not ret and not skipped:
  407. gbp.log.info("Version '%s' imported under '%s'" % (ver_str, spec.name))
  408. return ret
  409. if __name__ == '__main__':
  410. sys.exit(main(sys.argv))
  411. # vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: