#393 [WIP][Discussion] Adding a Layout module
Closed 3 years ago by mohanboddu. Opened 5 years ago by bstinson.
bstinson/rpkg add-layout  into  master

file modified
+22 -16
@@ -47,6 +47,7 @@ 

  from pyrpkg.lookaside import CGILookasideCache

  from pyrpkg.sources import SourcesFile

  from pyrpkg.utils import cached_property, log_result, find_me

+ from pyrpkg.layout import Layout

  

  PY26 = sys.version_info < (2, 7, 0)

  
@@ -241,6 +242,10 @@ 

          self._path = value

  

      @property

+     def layout(self):

+         return Layout.load(self.path)

+ 

+     @property

      def kojisession(self):

          """This property ensures the kojisession attribute"""

  
@@ -821,16 +826,17 @@ 

          self._distvar, self._distval = osver.split('-')

          self._distval = self._distval.replace('.', '_')

          self._disttag = 'el%s' % self._distval

-         self._rpmdefines = ["--define '_sourcedir %s'" % self.path,

-                             "--define '_specdir %s'" % self.path,

-                             "--define '_builddir %s'" % self.path,

-                             "--define '_srcrpmdir %s'" % self.path,

-                             "--define '_rpmdir %s'" % self.path,

+         self._rpmdefines = ["--define '_sourcedir %s'" % self.layout.sourcedir,

+                             "--define '_specdir %s'" % self.layout.specdir,

+                             "--define '_builddir %s'" % self.layout.builddir,

+                             "--define '_srcrpmdir %s'" % self.layout.srcrpmdir,

+                             "--define '_rpmdir %s'" % self.layout.rpmdir,

                              "--define 'dist .%s'" % self._disttag,

                              "--define '%s %s'" % (self._distvar,

                                                    self._distval.split('_')[0]),

                              # int and float this to remove the decimal

                              "--define '%s 1'" % self._disttag]

+         self.log.debug("RPMDefines: %s" % self._rpmdefines)

  

      @property

      def spec(self):
@@ -843,19 +849,19 @@ 

      def load_spec(self):

          """This sets the spec attribute"""

  

-         deadpackage = False

+         # For all layouts, check for dead.pkg in the root of the package dir

+         files = os.listdir(self.path)

+         for f in files:

+             if f == 'dead.package':

+                 raise rpkgError('No spec file found. This package is retired')

  

          # Get a list of files in the path we're looking at

-         files = os.listdir(self.path)

+         files = os.listdir(self.layout.specdir)

          # Search the files for the first one that ends with ".spec"

          for f in files:

              if f.endswith('.spec') and not f.startswith('.'):

-                 self._spec = f

+                 self._spec = os.path.join(self.layout.specdir, f)

                  return

-             if f == 'dead.package':

-                 deadpackage = True

-         if deadpackage:

-             raise rpkgError('No spec file found. This package is retired')

          else:

              raise rpkgError('No spec file found.')

  
@@ -1067,7 +1073,7 @@ 

  

      @property

      def sources_filename(self):

-         return os.path.join(self.path, 'sources')

+         return os.path.join(self.path, self.layout.sources_file_template.format(self))

  

      @property

      def osbs_config_filename(self):
@@ -2750,7 +2756,7 @@ 

          for f in files:

              # TODO: Skip empty file needed?

              file_hash = self.lookasidecache.hash_file(f)

-             file_basename = os.path.basename(f)

+             file_basename = f.replace(self.path + '/', '')

  

              try:

                  sourcesf.add_entry(self.lookasidehash, file_basename,
@@ -2777,7 +2783,7 @@ 

          sourcesf.write()

          gitignore.write()

  

-         self.repo.index.add(['sources', '.gitignore'])

+         self.repo.index.add([sourcesf.sourcesfile, '.gitignore'])

  

      def prep(self, arch=None, builddir=None, buildrootdir=None):

          """Run ``rpmbuild -bp``
@@ -2818,7 +2824,7 @@ 

              digests.

          """

  

-         self.srpmname = os.path.join(self.path,

+         self.srpmname = os.path.join(self.layout.srcrpmdir,

                                       "%s-%s-%s.src.rpm"

                                       % (self.repo_name, self.ver, self.rel))

  

file added
+79
@@ -0,0 +1,79 @@ 

+ # Copyright (c) 2018 - Red Hat Inc.

+ #

+ # This program is free software; you can redistribute it and/or modify it

+ # under the terms of the GNU General Public License as published by the

+ # Free Software Foundation; either version 2 of the License, or (at your

+ # option) any later version.  See http://www.gnu.org/copyleft/gpl.html for

+ # the full text of the license.

+ 

+ """Work with different source layouts

+ 

+ This module contains configuration objects that correspond to differnet

+ dist-git source layouts. Distributions like Fedora and RHEL use the standard

+ dist-git layout while CentOS uses the Exploded SRPM layout.

+ """

+ 

+ import logging

+ import os

+ 

+ log = logging.getLogger(__name__)

+ 

+ 

+ class Layout(object):

+     subclasses = {}

+ 

+     def __init__(self, rpkg_path):

+         self.sourcedir = rpkg_path

+         self.specdir = rpkg_path

+         self.builddir = rpkg_path

+         self.rpmdir = rpkg_path

+         self.srcrpmdir = rpkg_path

+         self.sources_file_template = 'sources'

+ 

+     @classmethod

+     def subclass(cls, sources_filename_substring):

+         def decorator(subcls):

+             cls.subclasses[sources_filename_substring] = subcls

+             return subcls

+         return decorator

+ 

+     @classmethod

+     def load(cls, path=os.getcwd()):

+         files = os.listdir(path)

+ 

+         for substring_key in cls.subclasses:

+             for filename in files:

+                 if substring_key in filename:

+                     return cls.subclasses[substring_key](path)

+         return cls(path)

+ 

+ 

+ @Layout.subclass('sources')

+ class DistGitLayout(Layout):

+     def __init__(self, rpkg_path):

+         self.site_name = 'fedpkg'

+         self.sourcedir = rpkg_path

+         self.specdir = rpkg_path

+         self.builddir = rpkg_path

+         self.rpmdir = rpkg_path

+         self.srcrpmdir = rpkg_path

+         self.sources_file_template = 'sources'

+         log.debug("Subclass: DistGitLayout")

+         log.debug("Site: %s", self.site_name)

+ 

+ 

+ @Layout.subclass('.metadata')

+ class ExplodedSRPMLayout(Layout):

+ 

+     def __init__(self, rpkg_path):

+         self.sourcedir = os.path.join(rpkg_path, "SOURCES")

+         self.specdir = os.path.join(rpkg_path, "SPECS")

+         self.builddir = os.path.join(rpkg_path, "BUILD")

+         self.rpmdir = os.path.join(rpkg_path, "RPMS")

+         self.srcrpmdir = os.path.join(rpkg_path, "SRPMS")

+         self.site_name = 'centpkg'

+         # If we call str.format() from the command object with `self` as the

+         # parameter we can can get any of the attributes (including properties)

+         self.sources_file_template = '.{0.repo_name}.metadata'

+         log.debug("Subclass: ExplodedSRPMLayout")

+         log.debug("Site: %s", self.site_name)

file modified
+1 -1
@@ -920,7 +920,7 @@ 

              cli.gimmespec()

  

          output = sys.stdout.getvalue().strip()

-         self.assertEqual('docpkg.spec', output)

+         self.assertEqual(os.path.join(self.cloned_repo_path, 'docpkg.spec'), output)

  

  

  class TestClean(CliTestCase):

file modified
+1 -1
@@ -400,7 +400,7 @@ 

  

      def test_spec(self):

          cmd = self.make_commands()

-         self.assertEqual('docpkg.spec', cmd.spec)

+         self.assertEqual(os.path.join(self.cloned_repo_path, 'docpkg.spec'), cmd.spec)

  

      def test_no_spec_as_it_is_deadpackage(self):

          with patch('os.listdir', return_value=['dead.package']):

file added
+80
@@ -0,0 +1,80 @@ 

+ import os

+ import shutil

+ import tempfile

+ import unittest

+ 

+ from pyrpkg.layout import Layout

+ 

+ class EmptyLayoutTestCase(unittest.TestCase):

+     def setUp(self):

+         self.workdir = tempfile.mkdtemp(prefix='rpkg-tests.')

+         self.layout = Layout.load(self.workdir)

+ 

+     def tearDown(self):

+         shutil.rmtree(self.workdir)

+ 

+ 

+     def test_empty_layout_sets_sourcedir_to_cwd(self):

+         self.assertEqual(self.layout.sourcedir, self.workdir)

+ 

+     def test_empty_layout_sets_specdir_to_cwd(self):

+         self.assertEqual(self.layout.specdir, self.workdir)

+ 

+     def test_empty_layout_sets_builddir_to_cwd(self):

+         self.assertEqual(self.layout.builddir, self.workdir)

+ 

+     def test_empty_layout_sets_rpmdir_to_cwd(self):

+         self.assertEqual(self.layout.rpmdir, self.workdir)

+ 

+     def test_distgit_layout_sets_srcpmdir_to_cwd(self):

+         self.assertEqual(self.layout.srcrpmdir, self.workdir)

+ 

+ class DistGitLayoutTestCase(unittest.TestCase):

+     def setUp(self):

+         self.workdir = tempfile.mkdtemp(prefix='rpkg-tests.')

+         self.file = os.path.join(self.workdir,'sources')

+         open(self.file, 'a').close()

+         self.layout = Layout.load(self.workdir)

+ 

+     def tearDown(self):

+         shutil.rmtree(self.workdir)

+ 

+     def test_distgit_layout_sets_sourcedir_to_cwd(self):

+         self.assertEqual(self.layout.sourcedir, self.workdir)

+ 

+     def test_distgit_layout_sets_specdir_to_cwd(self):

+         self.assertEqual(self.layout.specdir, self.workdir)

+ 

+     def test_distgit_layout_sets_builddir_to_cwd(self):

+         self.assertEqual(self.layout.builddir, self.workdir)

+ 

+     def test_distgit_layout_sets_rpmdir_to_cwd(self):

+         self.assertEqual(self.layout.rpmdir, self.workdir)

+ 

+     def test_distgit_layout_sets_srcpmdir_to_cwd(self):

+         self.assertEqual(self.layout.srcrpmdir, self.workdir)

+ 

+ class ExplodedSRPMLayoutTestCase(unittest.TestCase):

+     def setUp(self):

+         self.workdir = tempfile.mkdtemp(prefix='rpkg-tests.')

+         self.file = os.path.join(self.workdir,'.test.metadata')

+         open(self.file, 'a').close()

+         self.layout = Layout.load(self.workdir)

+ 

+     def tearDown(self):

+         shutil.rmtree(self.workdir)

+ 

+     def test_distgit_layout_sets_sourcedir_to_SOURCES(self):

+         self.assertEqual(self.layout.sourcedir, os.path.join(self.workdir, 'SOURCES'))

+ 

+     def test_distgit_layout_sets_specdir_to_SPECS(self):

+         self.assertEqual(self.layout.specdir, os.path.join(self.workdir, 'SPECS'))

+ 

+     def test_distgit_layout_sets_builddir_to_BUILD(self):

+         self.assertEqual(self.layout.builddir, os.path.join(self.workdir, 'BUILD'))

+ 

+     def test_distgit_layout_sets_rpmdir_to_RPMS(self):

+         self.assertEqual(self.layout.rpmdir, os.path.join(self.workdir, 'RPMS'))

+ 

+     def test_distgit_layout_sets_srpmdir_to_SRPMS(self):

+         self.assertEqual(self.layout.srcrpmdir, os.path.join(self.workdir, 'SRPMS'))

Background

The Fedora and CentOS infrastructure teams are working on synchronizing branches between src.fedoraproject.org and git.centos.org this means that CentOS developers will see Fedora branches when they work on things, and Fedora developers will see the branches pushed from Red Hat into CentOS. Consider an example package a2ps

Fedora follows the dist-git layout:

 .
├── a2ps-4.13-autoenc.patch
├── a2ps-4.13b-attr.patch
├── a2ps-4.13b-encoding.patch
├── a2ps-4.13b-numeric.patch
├── a2ps-4.13b-tilde.patch
├── a2ps-4.13-conf.patch
├── a2ps-4.13-etc.patch
├── a2ps-4.13-eucjp.patch
├── a2ps-4.13-euckr.patch
├── a2ps-4.13-glibcpaper.patch
├── a2ps-4.13-gnusource.patch
 .... SOURCE files ommited for brevity...
├── a2ps.spec
└── sources

CentOS uses the exploded SRPM layout:

.
├── .a2ps.metadata
├── SOURCES
│   ├── a2ps-4.13-autoenc.patch
│   ├── a2ps-4.13b-attr.patch
│   ├── a2ps-4.13b-encoding.patch
│   ├── a2ps-4.13b-numeric.patch
│   ├── a2ps-4.13b-tilde.patch
│   ├── a2ps-4.13-conf.patch
│   ├── a2ps-4.13-etc.patch
│   ├── a2ps-4.13-eucjp.patch
 .... SOURCE files ommitted for brevity...
│   └── i18n-fonts-0.1.tar.gz
└── SPECS
    └── a2ps.spec

Proposal

We should define a layout object that contains configuration for all of the information about where the files go in a package directory. This would eventually allow us to use the same tool to work across Fedora and CentOS branches, and to translate between the two formats. Sometimes packagers might want to take a package branched for Fedora, and bring it into a Special Interest Group in CentOS (for example).

New layouts can be registered, we differentiate the layouts by specifying a file to look for in the root of the package directory to signify which layout to use. For the 2 existing layouts we search for the lookaside metadata file:
@Layout.subclass('sources'), for example, looks for the 'sources' file in a standard dist-git directory and registers the layout with the handler.

I've tested this current PR with centpkg and fedpkg, and it seems to support functionality as it exists (that is to say, no harm was done).

What's needed to remove the [WIP] tag?

  • Fix unit test failures
  • Finish modifying the various places where we put things in the package directory so that we use the layout configuration
  • Find a way to specify the site based on the layout. We want to push to the proper lookaside, and use the appropriate buildsystem based on what the layout tells us to do
  • Add a cli method to translate between different layouts

1 new commit added

  • check for dead.package sooner, before we start looking for a SRPM
5 years ago

2 new commits added

  • the specfile methods give a full path now, update the tests
  • provide a blank Layout for when the directory is not populated yet
5 years ago

1 new commit added

  • flake8 fixes
5 years ago

5 new commits added

  • flake8 fixes
  • the specfile methods give a full path now, update the tests
  • provide a blank Layout for when the directory is not populated yet
  • check for dead.package sooner, before we start looking for a SRPM
  • start on a module for differing package source layouts
5 years ago

In general this looks reasonable to me, but I don't think relying on sources file to identify the layout in Fedora is going to work 100 %. There are repos that don't have the file, like fedora-repos. Maybe the dist-git layout could be the default? Or it could check for presence of spec file?

Unrelated question: do you actually store tarballs in CentOS dist-git?

+1 for making the dist-git layout default, I'll push a change for that.

We don't store tarballs, the tree output above was from a dirty directory.

CentOS has our own lookaside cache, also with a different structure (it has a directory per branch under the package name).

What is site_name used for Layout? If it is necessary, it would be good to guess it from somewhere, probably cliClient.site, because there are other package tools built on top of rpkg, e.g. rhpkg and rfpkg, this hardcoded site name does not work for them.

I like this decorator design. :thumbsup: :)

A suggestion is to document Layout and its methods, although it's small and simple to understand by reading through the code.

What is site_name used for Layout? If it is necessary, it would be good to guess it from somewhere, probably cliClient.site, because there are other package tools built on top of rpkg, e.g. rhpkg and rfpkg, this hardcoded site name does not work for them.

Agreed, this shouldn't be hard-coded. What I'm trying to do here is to have a piece of configuration (or user input) to specify which site to use as a default for the individual layouts:

For example as a CentOS/Fedora developer, I probably want ExplodedSRPMLayout to inherit the lookaside configuration, koji config, etc. from centpkg and DistGitLayout to inherit from fedpkg.

OR

as a RHEL developer, I probably want ExplodedSRPMLayout to inherit those from centpkg and have DistGitLayout inherit configs from rhpkg

4 new commits added

  • we can get the sources file from the layout now
  • In upload, we actually want the relative path to the file not the
  • make the DistGit properties the default if a layout cannot be detected
  • add some tests
5 years ago

For what it's worth, Mageia's Dist-SVN layout is similar to CentOS' Dist-Git layout, while openSUSE's layout is similar to Fedora's. Could we call these layouts something generic, like "flat" for the Fedora/openSUSE style, and "rpmbuild" for the CentOS/Mageia style?

These layouts aren't even specific to Git or SVN or any VCS, just a style of recording package sources.

@ngompa are the formats exactly alike? If so we can come up with something more general (ExplodedSRPM is what we've been calling rpmbuild layout for a while, but I'm flexible).

Mageia's layout is like so:

$> tree dnf/
dnf/
├── SOURCES
│   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
│   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
│   ├── dnf-4.2.23.tar.gz
│   └── sha1.lst
└── SPECS
    └── dnf.spec

2 directories, 5 files

The sha1.lst file is equivalent to the sources file:

$> cat dnf/SOURCES/sha1.lst
0da07a3e6ff19430ffe39699e474439eab63ee7d  dnf-4.2.23.tar.gz

The tarballs are fetched as part of running mgarepo checkout dnf, and don't exist remotely.

The remote SVN layout is a bit more interesting...

$> svn co svn://svn.mageia.org/packages/cauldron/dnf
[...]
$> tree dnf/
dnf/
├── current
│   ├── SOURCES
│   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
│   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
│   │   └── sha1.lst
│   └── SPECS
│       └── dnf.spec
├── pristine
│   ├── SOURCES
│   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
│   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
│   │   └── sha1.lst
│   └── SPECS
│       └── dnf.spec
└── releases
    ├── 1.1.10
    │   ├── 1.mga6
    │   │   ├── SOURCES
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 2.mga6
    │       ├── SOURCES
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 1.1.2
    │   └── 5.mga6
    │       ├── SOURCES
    │       │   ├── dnf-test-assert-py35-fix.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 1.1.3
    │   ├── 1.mga6
    │   │   ├── SOURCES
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 2.mga6
    │       ├── SOURCES
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 1.1.5
    │   └── 1.mga6
    │       ├── SOURCES
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 1.1.6
    │   ├── 1.mga6
    │   │   ├── SOURCES
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 2.mga6
    │       ├── SOURCES
    │       │   ├── fix-dnf-history-traceback.patch
    │       │   ├── sha1.lst
    │       │   └── zanata-update.patch
    │       └── SPECS
    │           └── dnf.spec
    ├── 1.1.7
    │   └── 1.mga6
    │       ├── SOURCES
    │       │   ├── fix-empty-history-cmd.patch
    │       │   ├── restore-basearch-needed-by-koji.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 1.1.8
    │   ├── 1.mga6
    │   │   ├── SOURCES
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 2.mga6
    │       ├── SOURCES
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 1.1.9
    │   ├── 1.mga6
    │   │   ├── SOURCES
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 2.mga6
    │   │   ├── SOURCES
    │   │   │   ├── enforce-api-add-compatibility-methods-for-renamed-co.patch
    │   │   │   ├── enforce-api-reflect-changes-from-992475-in-completio.patch
    │   │   │   ├── Revert-group-treat-mandatory-pkgs-as-mandatory-if-st.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 3.mga6
    │       ├── SOURCES
    │       │   ├── enforce-api-add-compatibility-methods-for-renamed-co.patch
    │       │   ├── enforce-api-reflect-changes-from-992475-in-completio.patch
    │       │   ├── MGA-Remove-message-on-how-to-transfer-data-from-yum-to-dnf.patch
    │       │   ├── Revert-group-treat-mandatory-pkgs-as-mandatory-if-st.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.0.0
    │   ├── 0.1.rc1.mga6
    │   │   ├── SOURCES
    │   │   │   ├── 0001-convert-history-args-to-transaction-ids.patch
    │   │   │   ├── 0001-repo-add-rpm-as-alias-for-rpm-md-RhBug-1380580.patch
    │   │   │   ├── 0001-Revert-group-treat-mandatory-pkgs-as-mandatory-if-st.patch
    │   │   │   ├── 0001-Solve-problems-with-l-and-s-options-in-repoquery.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 0.2.rc1.mga6
    │   │   ├── SOURCES
    │   │   │   ├── 0001-convert-history-args-to-transaction-ids.patch
    │   │   │   ├── 0001-Fix-query-glob-optimization-after-privatization.patch
    │   │   │   ├── 0001-repo-add-rpm-as-alias-for-rpm-md-RhBug-1380580.patch
    │   │   │   ├── 0001-Revert-group-treat-mandatory-pkgs-as-mandatory-if-st.patch
    │   │   │   ├── 0001-Solve-problems-with-l-and-s-options-in-repoquery.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 0.3.rc1.mga6
    │   │   ├── SOURCES
    │   │   │   ├── 0001-convert-history-args-to-transaction-ids.patch
    │   │   │   ├── 0001-Fix-query-glob-optimization-after-privatization.patch
    │   │   │   ├── 0001-repo-add-rpm-as-alias-for-rpm-md-RhBug-1380580.patch
    │   │   │   ├── 0001-Revert-group-treat-mandatory-pkgs-as-mandatory-if-st.patch
    │   │   │   ├── 0001-Solve-problems-with-l-and-s-options-in-repoquery.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 0.4.rc1.mga6
    │   │   ├── SOURCES
    │   │   │   ├── 0001-convert-history-args-to-transaction-ids.patch
    │   │   │   ├── 0001-Fix-query-glob-optimization-after-privatization.patch
    │   │   │   ├── 0001-repo-add-rpm-as-alias-for-rpm-md-RhBug-1380580.patch
    │   │   │   ├── 0001-repo-don-t-pass-None-value-to-librepo-callback-RhBug.patch
    │   │   │   ├── 0001-Revert-group-treat-mandatory-pkgs-as-mandatory-if-st.patch
    │   │   │   ├── 0001-Solve-problems-with-l-and-s-options-in-repoquery.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 0.5.rc2.mga6
    │   │   ├── SOURCES
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 0.6.rc2.mga6
    │   │   ├── SOURCES
    │   │   │   ├── 0001-Ensure-that-repo-ID-is-only-used-when-repo-name-is-N.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 1.mga6
    │       ├── SOURCES
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.1.0
    │   ├── 1.mga6
    │   │   ├── SOURCES
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 2.mga6
    │       ├── SOURCES
    │       │   ├── dnf-2.1.0-Add-armv5tl-to-arm-basearch.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.1.1
    │   └── 1.mga6
    │       ├── SOURCES
    │       │   ├── dnf-2.1.0-Add-armv5tl-to-arm-basearch.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.2.0
    │   └── 1.mga6
    │       ├── SOURCES
    │       │   ├── dnf-2.1.0-Add-armv5tl-to-arm-basearch.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.3.0
    │   └── 1.mga6
    │       ├── SOURCES
    │       │   ├── dnf-2.1.0-Add-armv5tl-to-arm-basearch.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.4.0
    │   └── 1.mga6
    │       ├── SOURCES
    │       │   ├── 0001-Filter-out-src-for-get_best_selector.patch
    │       │   ├── PR800-fix-typo-in-supplements.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.4.1
    │   └── 1.mga6
    │       ├── SOURCES
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.5.0
    │   ├── 1.mga6
    │   │   ├── SOURCES
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 2.mga6
    │   │   ├── SOURCES
    │   │   │   ├── dnf-2.5.0-mga-run-makecache-only-if-cache-exists.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 3.mga6
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.5.1
    │   ├── 1.mga6
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 2.mga6
    │       ├── SOURCES
    │       │   ├── 0001-Add-detection-if-mirrorlist-is-used-for-metalink.patch
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.6.2
    │   ├── 1.mga7
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 3.mga7
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.6.3
    │   └── 1.mga7
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 2.7.5
    │   └── 1.mga7
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 3.0.2
    │   └── 1.mga7
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 3.1.0
    │   └── 1.mga7
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 3.5.1
    │   ├── 1.mga7
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 2.mga7
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 4.0.10
    │   └── 1.mga7
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   ├── PR1299-Use-correct-sphinx-build-binary.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 4.0.4
    │   ├── 1.mga7
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 2.mga7
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 3.mga7
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 4.2.17
    │   ├── 1.mga8
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 2.mga8
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 3.mga8
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 4.mga8
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 4.2.18
    │   ├── 1.mga8
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 2.mga8
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 4.2.19
    │   ├── 1.mga8
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 2.mga8
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   ├── 3.mga8
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 4.mga8
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 4.2.2
    │   ├── 1.mga7
    │   │   ├── SOURCES
    │   │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │   │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │   │   │   └── sha1.lst
    │   │   └── SPECS
    │   │       └── dnf.spec
    │   └── 2.mga7
    │       ├── SOURCES
    │       │   ├── 0001-Add-command-abbreviations-RhBug-1634232.patch
    │       │   ├── 0001-Fix-the-installation-of-completion_helper.py.patch
    │       │   ├── 0002-CMake-Actually-install-aliases.patch
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 4.2.23
    │   └── 1.mga8
    │       ├── SOURCES
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    ├── 4.2.5
    │   └── 1.mga7
    │       ├── SOURCES
    │       │   ├── 0001-Python2-3-compatibility-for-exceptions-representatio.patch
    │       │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
    │       │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
    │       │   └── sha1.lst
    │       └── SPECS
    │           └── dnf.spec
    └── 4.2.6
        ├── 1.mga7
        │   ├── SOURCES
        │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
        │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
        │   │   └── sha1.lst
        │   └── SPECS
        │       └── dnf.spec
        ├── 2.mga8
        │   ├── SOURCES
        │   │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
        │   │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
        │   │   └── sha1.lst
        │   └── SPECS
        │       └── dnf.spec
        └── 3.mga8
            ├── SOURCES
            │   ├── 1001-Disable-the-dnf-makecache-timer-for-Mageia-live-envi.patch
            │   ├── 1002-Run-the-makecache-service-timer-only-if-the-DNF-cach.patch
            │   └── sha1.lst
            └── SPECS
                └── dnf.spec

225 directories, 242 files

The checkout done by mgarepo checkout dnf is identical to me doing svn co svn://svn.mageia.org/packages/cauldron/dnf/current dnf. This layout is more or less identical to what CentOS does, minus the metadata file being different.

Pull-Request has been closed by mohanboddu

3 years ago