Blob Blame History Raw
diff --git a/AUTHORS b/AUTHORS
index 88003d1..c8ce939 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,6 +1,7 @@
 Ales Kozumplik <ales@redhat.com>
 Igor Gnatenko <i.gnatenko.brain@gmail.com>
 Jan Silhan <jsilhan@redhat.com>
+Michal Mraka <michael.mraka@redhat.com>
 Miroslav Suchý <msuchy@redhat.com>
 Parag Nemade <pnemade@redhat.com>
 Petr Špaček <pspacek@redhat.com>
diff --git a/dnf-plugins-core.spec b/dnf-plugins-core.spec
new file mode 100644
index 0000000..b9e0a38
--- /dev/null
+++ b/dnf-plugins-core.spec
@@ -0,0 +1,329 @@
+%{?!dnf_version: %global dnf_version 0.6.4}
+
+Name:		dnf-plugins-core
+Version:	0.1.5
+Release:	2%{?dist}
+Summary:	Core Plugins for DNF
+Group:		System Environment/Base
+License:	GPLv2+
+URL:		https://github.com/rpm-software-management/dnf-plugins-core
+
+# source archive is created by running package/archive from a git checkout
+Source0:	dnf-plugins-core-%{version}.tar.gz
+
+BuildArch:	noarch
+BuildRequires:	cmake
+BuildRequires:	dnf = %{dnf_version}
+BuildRequires:	gettext
+BuildRequires:	pykickstart
+BuildRequires:	python-nose
+BuildRequires:	python-sphinx
+BuildRequires:	python2-devel
+Requires:	dnf = %{dnf_version}
+Requires:	pykickstart
+Requires:	python-requests
+
+%description
+Core Plugins for DNF. This package enhance DNF with builddep, config-manager,
+copr, debuginfo-install, download, kickstart, needs-restarting, repoquery and
+reposync commands. Additionally provides generate_completion_cache, noroot and
+protected_packages passive plugins.
+
+%package -n python3-dnf-plugins-core
+Summary:	Core Plugins for DNF
+Group:		System Environment/Base
+BuildRequires:	python3-devel
+BuildRequires:	python3-dnf = %{dnf_version}
+BuildRequires:	python3-nose
+BuildRequires:	python3-sphinx
+Requires:	python3-dnf = %{dnf_version}
+
+%description -n python3-dnf-plugins-core
+Core Plugins for DNF, Python 3 version. This package enhance DNF with builddep,
+config-manager, copr, debuginfo-install, download, kickstart, needs-restarting,
+repoquery and reposync commands. Additionally provides generate_completion_cache,
+noroot and protected_packages passive plugins.
+
+%prep
+%setup -q -n dnf-plugins-core-%{version}
+rm -rf py3
+mkdir ../py3
+cp -a . ../py3/
+mv ../py3 ./
+
+%build
+%cmake .
+make %{?_smp_mflags}
+make doc-man
+pushd py3
+%cmake -DPYTHON_DESIRED:str=3 .
+make %{?_smp_mflags}
+make doc-man
+popd
+
+%install
+make install DESTDIR=$RPM_BUILD_ROOT
+%find_lang %{name}
+pushd py3
+make install DESTDIR=$RPM_BUILD_ROOT
+popd
+
+%check
+PYTHONPATH=./plugins /usr/bin/nosetests-2.* -s tests/
+PYTHONPATH=./plugins /usr/bin/nosetests-3.* -s tests/
+
+%files -f %{name}.lang
+%doc AUTHORS COPYING README.rst
+%dir %{_sysconfdir}/dnf/protected.d
+%ghost %{_var}/cache/dnf/packages.db
+%{python_sitelib}/dnf-plugins/*
+%{python_sitelib}/dnfpluginscore/
+%{_mandir}/man8/dnf.plugin.*
+
+%files -n python3-dnf-plugins-core -f %{name}.lang
+%doc AUTHORS COPYING README.rst
+%dir %{_sysconfdir}/dnf/protected.d
+%ghost %{_var}/cache/dnf/packages.db
+%exclude %{python3_sitelib}/dnf-plugins/__pycache__/
+%{python3_sitelib}/dnf-plugins/*
+%{python3_sitelib}/dnf-plugins/__pycache__/*
+%{python3_sitelib}/dnfpluginscore/
+%{_mandir}/man8/dnf.plugin.*
+
+%changelog
+* Mon Apr 13 2015 Michal Luscon <mluscon@redhat.com> 0.1.5-2
+- prepare repo for tito build system (Michal Luscon)
+- migrate raw_input() to Python3 (RhBug:1208399) (Miroslav Suchý)
+- create --destdir if not exist (Michael Mraka)
+- repoquery: Added -s/--source switch, test case and documentation for querying source rpm name (Parag Nemade)
+- repoquery: Added documentation and test case for file switch (Parag Nemade)
+- debuginfo-install: support cases where src.rpm name != binary package name (Petr Spacek)
+- use dnfpluginscore.lib.urlopen() (RhBug:1193047) (Miroslav Suchý)
+- implemented functionality of yum-config-manager (Michael Mraka)
+- repoquery: Added --file switch to show who owns the given file (RhBug:1196952) (Parag Nemade)
+- debuginfo-install: accept packages names specified as NEVRA (RhBug:1171046) (Petr Spacek)
+- repoquery: accept package names specified as NEVRA (RhBug:1179366) (Petr Spacek)
+- download: fix typo in 'No source rpm definded' (Petr Spacek)
+- download: accept package names ending with .src too (Petr Spacek)
+- cosmetic: download: pylint fixes (Jan Silhan)
+- download: Do not disable user-enabled repos (thanks Spacekpe) (Jan Silhan)
+- let pylint ignore unused variables starting with _ (Michael Mraka)
+- updated copyright (Michael Mraka)
+- fixed pylint warning Redefining name from outer scope (Michael Mraka)
+- fixed pylint warning Invalid constant name (Michael Mraka)
+- fixed pylint warnings Line too long (Michael Mraka)
+- pylint fix for Wrong continued indentation (Michael Mraka)
+- updated copyright (Michael Mraka)
+- pylint fix for Specify string format arguments as logging function parameters (Michael Mraka)
+- pylint fix for Method could be a function (Michael Mraka)
+- pylint fix for  __init__ method from base class is not called (Michael Mraka)
+- pylint fix for Attribute defined outside __init__ (Michael Mraka)
+- updated copyright (Michael Mraka)
+- pylint fix for  __init__ method from base class is not called (Michael Mraka)
+- pylint fix for Wrong continued indentation (Michael Mraka)
+- updated copyright (Michael Mraka)
+- pylint fix for Method could be a function (Michael Mraka)
+- pylint fix for  __init__ method from base class is not called (Michael Mraka)
+- updated copyright (Michael Mraka)
+- pylint fix for Unused import (Michael Mraka)
+- pylint fix for  __init__ method from base class is not called (Michael Mraka)
+- updated copyright (Michael Mraka)
+- pylint fix for Unused import (Michael Mraka)
+- Add README to tests/ directory (Petr Spacek)
+- AUTHORS: updated (Jan Silhan)
+- download: fix package download on Python 3 (Petr Spacek)
+
+* Thu Feb 5 2015 Jan Silhan <jsilhan@redhat.com> - 0.1.5-1
+- updated package url (Michael Mraka)
+- also dnf_version could be specified on rpmbuild commandline (Michael Mraka)
+- simple script to build test package (Michael Mraka)
+- let gitrev be specified on rpmbuild commandline (Michael Mraka)
+- assign default GITREV value (Michael Mraka)
+- standard way to find out latest commit (Michael Mraka)
+- debuginfo-install: fix handling of subpackages with non-zero epoch (Petr Spacek)
+- debuginfo-install: Make laywers happier by assigning copyright to Red Hat (Petr Spacek)
+- debuginfo-install: remove dead code uncovered by variable renaming (Petr Spacek)
+- debuginfo-install: clearly separate source and debug package names (Petr Spacek)
+- debuginfo-install: use descriptive parameter name in _is_available() (Petr Spacek)
+- repoquery: add -l option to list files contained in the package (Petr Spacek)
+- 1187773 - replace undefined variable (Miroslav Suchý)
+- download: fixed unicode location error (RhBug:1178239) (Jan Silhan)
+- builddep recognizes nosrc.rpm pkgs (RhBug:1166126) (Jan Silhan)
+- builddep: added nosignatures flag to rpm transaction set (Jan Silhan)
+- builddep: more verbose output of non-matching packages (RhBug:1155211) (Jan Silhan)
+- package: archive script is the same as in dnf (Jan Silhan)
+- spec: exclude __pycache__ dir (Igor Gnatenko)
+
+* Fri Dec 5 2014 Jan Silhan <jsilhan@redhat.com> - 0.1.4-1
+- revert of commit 80ae3f4 (Jan Silhan)
+- transifex update (Jan Silhan)
+- spec: binded to current dnf version (Jan Silhan)
+- generate_completion_cache: use sqlite instead of text files (Igor Gnatenko)
+- logging: renamed log file (Related:RhBug:1074715) (Jan Silhan)
+- Add reposync. (RhBug:1139738) (Ales Kozumplik)
+- download: fix traceback if rpm package has no defined sourcerpm (RhBug: 1144003) (Tim Lauridsen)
+- lint: ignore warnings of a test accessing protected attribute. (Ales Kozumplik)
+- repoquery lint: logger is not used. (Ales Kozumplik)
+- repoquery: support querying of weak deps. (Ales Kozumplik)
+- needs_restarting: fix typo (Miroslav Suchý)
+- copr: migrate copr plugin form urlgrabber to python-request (Miroslav Suchý)
+- Add needs-restarting command. (Ales Kozumplik)
+
+* Thu Sep 4 2014 Jan Silhan <jsilhan@redhat.com> - 0.1.3-1
+- repoquery: output times in UTC. (Ales Kozumplik)
+- repoquery: missing help messages. (Ales Kozumplik)
+- repoquery: add --info. (RhBug:1135984) (Ales Kozumplik)
+- add Jan to AUTHORS. (Ales Kozumplik)
+- spec: extended package description with plugin names and commands (Related:RhBug:1132335) (Jan Silhan)
+- copr: check for 'ok' in 'output' for json data (RhBug:1134378) (Igor Gnatenko)
+- README: changed references to new repo location (Jan Silhan)
+- transifex update (Jan Silhan)
+- copr: convert key to unicode before guessing lenght (Miroslav Suchý)
+- Add pnemade to AUTHORS (Ales Kozumplik)
+- debuginfo-install: Use logger as module level variable and not instance attribute since dnf-0.6.0 release (RhBug:1130559) (Parag Nemade)
+- copr: Use logger as module level variable and not instance attribute since dnf-0.6.0 release (RhBug:1130559) (Parag Nemade)
+- copr: implement help command (Igor Gnatenko)
+- debuginfo-install: fix indenting (Igor Gnatenko)
+- debuginfo-install: use srpm basename for debuginfo (Igor Gnatenko)
+
+* Mon Jul 28 2014 Aleš Kozumplík <ales@redhat.com> - 0.1.2-1
+- BashCompletionCache: error strings are unicoded (RhBug:1118809) (Jan Silhan)
+- transifex update (Jan Silhan)
+- debuginfo-install: remove some pylint warnings (Igor Gnatenko)
+- debuginfo-install: fix installing when installed version not found in repos, optimize performance (RhBug: 1108321) (Ig
+- fix: copr plugin message for repo without builds (RhBug:1116389) (Adam Samalik)
+- logging: remove messages about initialization. (Ales Kozumplik)
+
+* Thu Jul 3 2014 Aleš Kozumplík <ales@redhat.com> - 0.1.1-2
+- packaging: add protected_packages.py to the package. (Ales Kozumplik)
+
+* Thu Jul 3 2014 Aleš Kozumplík <ales@redhat.com> - 0.1.1-1
+- protected_packages: prevent removal of the running kernel. (RhBug:1049310) (Ales Kozumplik)
+- packaging: create and own /etc/dnf/protected.d. (Ales Kozumplik)
+- doc: add documentation for protected_packages. (Ales Kozumplik)
+- doc: rename: generate-completion-cache -> generate_completion_cache. (Ales Kozumplik)
+- add protected_packages (RhBug:1111855) (Ales Kozumplik)
+- build: add python-requests to requires (RHBZ: 1104088) (Miroslav Suchý)
+- doc: typo: fix double 'plugin' in release notes. (Ales Kozumplik)
+
+* Wed Jun 4 2014 Aleš Kozumplík <ales@redhat.com> - 0.1.0-1
+- pylint: fix all pylint builddep problems. (Ales Kozumplik)
+- builddep: better error reporting on deps that actually don't exist. (Ales Kozumplik)
+- builddep: load available repos. (RhBug:1103906) (Ales Kozumplik)
+- tests: stop argparse from printing to stdout when tests run. (Ales Kozumplik)
+- packaging: all the manual pages with a glob. (Ales Kozumplik)
+- fix: packaging problem with query.py. (Ales Kozumplik)
+- doc: add reference documentation for repoquery. (Ales Kozumplik)
+- repoquery: support --provides, --requires etc. (Ales Kozumplik)
+- repoquery: make the CLI more compatible with Yum's repoquery. (Ales Kozumplik)
+- repoquery: some cleanups in the plugin and the tests. (Ales Kozumplik)
+- rename: query->repoquery. (RhBug:1045078) (Ales Kozumplik)
+- add pylint script for dnf-core-plugins. (Ales Kozumplik)
+- tests: repoquery: fix unit tests. (Ales Kozumplik)
+- add query tool (Tim Lauridsen)
+
+* Wed May 28 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.8-1
+- build: add sphinx to build requires. (Ales Kozumplik)
+- doc: packaging: add license block to each .rst. (Ales Kozumplik)
+- tests: stray print() in test_download.py. (Ales Kozumplik)
+- doc: put each synopsis on new line (Miroslav Suchý)
+- doc: cosmetic: project name in the documentation. (Ales Kozumplik)
+- doc: cleanups, form, style. (Ales Kozumplik)
+- doc: add documentation and man pages (Tim Lauridsen)
+- copr: remove repofile if failed to enable repo (Igor Gnatenko)
+- copr: honor -y and --assumeno (Miroslav Suchý)
+- py3: absolute imports and unicode literals everywhere. (Ales Kozumplik)
+- debuginfo-install: doesn't install latest pkgs (RhBug: 1096507) (Igor Gnatenko)
+- debuginfo-install: fix description (Igor Gnatenko)
+- debuginfo-install: fix logger debug messages (Igor Gnatenko)
+- build: install the download plugin (Tim Lauridsen)
+- download: update the download plugin with --source, --destdir & --resolve options (Tim Lauridsen)
+- Add a special ArgumentParser to parsing plugin cmd arguments and options (Tim Lauridsen)
+- tests: add __init__.py to make tests a module and use abs imports (Tim Lauridsen)
+- build: simplify plugins/CMakeLists.txt. (Ales Kozumplik)
+- dnf.cli.commands.err_mini_usage() changed name. (Ales Kozumplik)
+- kickstart: do not include kickstart errors into own messages. (Radek Holy)
+
+* Wed Apr 23 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.7-1
+- build: gettext is also needed as a buildreq (Tim Lauridsen)
+- copr: use usage & summary class attributes, to work with dnf 0.5.0 use shared lib dnfpluginscore for translation wrapp
+- build: add cmake as buildreq (Tim Lauridsen)
+- generate-completion-cache: fix shared lib name (Tim Lauridsen)
+- make .spec use gitrev in the source file add helper script for building source archive (Tim Lauridsen)
+- Added transifex config (Tim Lauridsen)
+- tests: use cli logger in kickstart test (Tim Lauridsen)
+- Added translation .pot file Added da translation files so we have something to build & install (Tim Lauridsen)
+- Added CMake files Added CMake build to .spec & and added translation files handling (Tim Lauridsen)
+- make plugins use shared lib added translation wrappers added missing usage & summary PEP8 fixes (Tim Lauridsen)
+- added shared dnfpluginscore lib (Tim Lauridsen)
+- copr: C:139, 0: Unnecessary parens after 'print' keyword (superfluous-parens) (Miroslav Suchý)
+- copr: W: 23, 0: Unused import gettext (unused-import) (Miroslav Suchý)
+- copr: C: 33, 0: No space allowed before : (Miroslav Suchý)
+- copr: some python3 migration (Miroslav Suchý)
+- copr: get rid of dnf i18n imports (Miroslav Suchý)
+- remove dnf.yum.i18n imports. (Ales Kozumplik)
+- copr: Fix the playground upgrade command. (Tadej Janež)
+- copr: implement search function (Igor Gnatenko)
+- better format output (Miroslav Suchý)
+- implement playground plugin (Miroslav Suchý)
+- move removing of repo into method (Miroslav Suchý)
+- check root only for actions which really need root (Miroslav Suchý)
+- move repo downloading into separate method (Miroslav Suchý)
+- define copr url as class attribute (Miroslav Suchý)
+- better wording of warning (Miroslav Suchý)
+- move question to function argument (Miroslav Suchý)
+- move guessing chroot into function (Miroslav Suchý)
+- copr: use common lib use Command.usage & summary cleanup imports & PEP8 fixes (Tim Lauridsen)
+- builddep: added usage & summary & fix some PEP8 issues (Tim Lauridsen)
+- kickstart: use new public Command.usage & Command.summary api (Tim Lauridsen)
+- fix resource leak in builddep.py. (Ales Kozumplik)
+- refactor: command plugins use demands mechanism. (Ales Kozumplik)
+- noroot: move to the new 'demands' mechanism to check the need of root. (Ales Kozumplik)
+- tests: fix locale independence. (Radek Holy)
+- [copr] correctly specify chroot when it should be guessed (Miroslav Suchý)
+
+* Mon Mar 17 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.6-1
+- clenaup: remove commented out code (Miroslav Suchý)
+- copr: list: print description (Igor Gnatenko)
+- builddep: rpm error messages sink. (Ales Kozumplik)
+- builddep: improve error handling on an command argument (RhBug:1074436) (Ales Kozumplik)
+- copr: handling case when no argument is passed on cli (Miroslav Suchý)
+- copr: delete excess argument (Igor Gnatenko)
+- add copr plugin (Miroslav Suchý)
+- debuginfo-install: check for root with dnf api (Igor Gnatenko)
+- packaging: fix bogus dates. (Ales Kozumplik)
+
+* Wed Feb 26 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.5-2
+- packaging: add debuginfo-install.py (Ales Kozumplik)
+
+* Wed Feb 26 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.5-1
+- packaging: add builddep.py to the RPM. (Ales Kozumplik)
+
+* Tue Feb 25 2014 Radek Holý <rholy@redhat.com> - 0.0.4-1
+- refactor: use Base.install instead of installPkgs in kickstart plugin. (Radek Holy)
+- refactor: move kickstart arguments parsing to standalone method. (Radek Holy)
+- tests: test effects instead of mock calls. (Radek Holy)
+- Add debuginfo-install plugin. (RhBug:1045770) (Igor Gnatenko)
+- builddep: needs to be run under root. (RhBug:1065851) (Ales Kozumplik)
+
+* Thu Feb 6 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.3-1
+- tests: import mock through support so its simpler for the test cases. (Ales Kozumplik)
+- packaging: fix typos in the spec. (Ales Kozumplik)
+- [completion_cache] Cache installed packages, update the cache less frequently (Elad Alfassa)
+- Add bash completion to dnf (Elad Alfassa)
+- packaging: missing buildrequire (Ales Kozumplik)
+
+* Mon Jan 13 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.2-1
+- First release.
+
+* Wed Jan 8 2014 Cristian Ciupitu <cristian.ciupitu@yahoo.com> - 0.0.1-4
+- Spec updates.
+
+* Tue Jan 7 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.1-3
+- Spec updates.
+
+* Mon Jan 6 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.1-2
+- Spec updates.
+
+* Fri Dec 20 2013 Aleš Kozumplík <ales@redhat.com> - 0.0.1-1
+- The initial package version.
diff --git a/doc/conf.py b/doc/conf.py
index f2741a4..f1976ae 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -56,7 +56,7 @@ copyright = u'2014, Red Hat, Licensed under GPLv2+'
 # The short X.Y version.
 
 def version_readout():
-    fn = os.path.join(_dirname, '../package/dnf-plugins-core.spec')
+    fn = os.path.join(_dirname, '../dnf-plugins-core.spec')
     with open(fn) as f:
         lines = f.readlines()
     for line in lines:
@@ -242,6 +242,8 @@ AUTHORS=[u'See AUTHORS in your Core DNF Plugins distribution']
 man_pages = [
     ('builddep', 'dnf.plugin.builddep', u'DNF builddep Plugin',
      AUTHORS, 8),
+    ('config_manager', 'dnf.plugin.config_manager',
+     u'DNF config-manager Plugin', AUTHORS, 8),
     ('copr', 'dnf.plugin.copr', u'DNF copr Plugin',
      AUTHORS, 8),
     ('debuginfo-install', 'dnf.plugin.debuginfo-install',
diff --git a/doc/config_manager.rst b/doc/config_manager.rst
new file mode 100644
index 0000000..cdfbf2d
--- /dev/null
+++ b/doc/config_manager.rst
@@ -0,0 +1,79 @@
+..
+  Copyright (C) 2015  Red Hat, Inc.
+
+  This copyrighted material is made available to anyone wishing to use,
+  modify, copy, or redistribute it subject to the terms and conditions of
+  the GNU General Public License v.2, or (at your option) any later version.
+  This program is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY expressed or implied, including the implied warranties of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+  Public License for more details.  You should have received a copy of the
+  GNU General Public License along with this program; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+  02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
+  source code or documentation are not subject to the GNU General Public
+  License and may only be used or replicated with the express permission of
+  Red Hat, Inc.
+
+==========================
+ DNF config-manager Plugin
+==========================
+
+Manage main DNF configuration options, toggle which
+repositories are enabled or disabled, and add new repositories.
+
+--------
+Synopsis
+--------
+
+``dnf config-manager [options] <repo>...``
+
+---------
+Arguments
+---------
+
+``<repo>``
+    Display / modify specified repository. If not specified display / modify main DNF configuration.
+
+-------
+Options
+-------
+
+``--help-cmd``
+    Show this help.
+
+``--add-repo=URL``
+    Add (and enable) the repo from the specified file or url.
+
+``--dump``
+    Print dump of current configuration values to stdout.
+
+``--set-disabled``
+    Disable the specified repos (automatically saves).
+
+``--set-enabled``
+    Enable the specified repos (automatically saves).
+
+``--save``
+    Save the current options (useful with --setopt).
+
+--------
+Examples
+--------
+``dnf config-manager --add-repo http://example.com/some/additional.repo``
+    Download additional.repo and store it in repodir.
+
+``dnf config-manager --add-repo http://example.com/different/repo``
+    Create new repo file with http://example.com/different/repo as baseurl and enable it.
+
+``dnf config-manager``
+    Display main DNF configuration.
+
+``dnf config-manager repo``
+    Display configuration of repo.
+
+``dnf config-manager --set-enabled repo``
+    Enable repo and make the change permanent.
+
+``dnf config-manager --setopt proxy=http://proxy.example.com:3128/ repo1 repo2 --save``
+    Update proxy setting in repo1 and repo2 and make the change permanent.
diff --git a/doc/download.rst b/doc/download.rst
index d4653d8..9a24b42 100644
--- a/doc/download.rst
+++ b/doc/download.rst
@@ -42,7 +42,7 @@ Options
     Show this help.
 
 ``--source``
-    Download the source rpm.
+    Download the source rpm. Enables source repositories of all enabled binary repositories.
 
 ``--destdir``
     Download directory, default is the current directory (the directory must exist).
diff --git a/doc/index.rst b/doc/index.rst
index e1ee869..3c5b459 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -26,6 +26,7 @@ This documents core plugins of DNF:
 
    release_notes
    builddep
+   config_manager
    copr
    debuginfo-install
    download
diff --git a/doc/repoquery.rst b/doc/repoquery.rst
index 99a8eb7..e14f3ba 100644
--- a/doc/repoquery.rst
+++ b/doc/repoquery.rst
@@ -25,7 +25,7 @@ Query package information from Yum repositories.
 Synopsis
 --------
 
-``dnf repoquery [<select-options>] [<query-options>] [<pkg-name>]``
+``dnf repoquery [<select-options>] [<query-options>] [<pkg-spec>]``
 ``dnf repoquery --querytags``
 
 -----------
@@ -38,11 +38,18 @@ Description
 Select Options
 --------------
 
-Together with ``<pkg-name>``, control what packages are displayed in the output. If ``<pkg-name>`` is given, the set of resulting packages is limited to the ones with a matching name (globbing supported), else all packages are considered.
+Together with ``<pkg-spec>``, control what packages are displayed in the output. If ``<pkg-spec>`` is given, the set of resulting packages matching the specification. All packages are considered if no ``<pkg-spec>`` is specified.
+
+``<pkg-spec>``
+    Package specification like: name[-[epoch:]version[-release]][.arch]. See
+    http://dnf.readthedocs.org/en/latest/command_ref.html#specifying-packages
 
 ``--arch <arch>``
     Limit the resulting set only to packages of arch ``<arch>``.
 
+``-f <file>``, ``--file <file>``
+    Limit the resulting set only to package that owns ``<file>``.
+
 ``--repoid <id>``
     Limit the resulting set only to packages from repo identified by ``<id>``.
 
@@ -71,6 +78,9 @@ The following are mutually exclusive, i.e. at most one can be specified. If no q
 ``-l, --list``
     Show list of files in the package.
 
+``-s, --source``
+    Show package source RPM name.
+
 ``--obsoletes``
     Display capabilities that the package obsoletes. Same as ``--qf "%{obsoletes}``.
 
@@ -96,6 +106,14 @@ Display requires of all ligttpd packages::
 
     dnf repoquery --requires lighttpd
 
+Display source rpm of ligttpd package::
+
+    dnf repoquery --source lighttpd
+
+Display package name that owns the given file::
+
+    dnf repoquery --file /etc/lighttpd/lighttpd.conf
+
 Display name, architecture and the containing repository of all lighttpd packages::
 
     dnf repoquery --queryformat '%{name}.%{arch} : %{reponame}' lighttpd
diff --git a/package/archive b/package/archive
deleted file mode 100755
index 16f652e..0000000
--- a/package/archive
+++ /dev/null
@@ -1,11 +0,0 @@
-#! /bin/bash
-
-GITREV=${1:-$(git rev-parse --short HEAD)}
-# shorten to 7 characters
-GITREV=${GITREV:0:7}
-
-echo $GITREV
-
-TARGET_DIR=$HOME/rpmbuild/SOURCES
-mkdir -p $TARGET_DIR
-git archive ${GITREV} --prefix=dnf-plugins-core/ | xz > $TARGET_DIR/dnf-plugins-core-${GITREV}.tar.xz
diff --git a/package/build-test-rpm b/package/build-test-rpm
deleted file mode 100755
index 2241c8c..0000000
--- a/package/build-test-rpm
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-
-DIR=$(dirname "$0")
-GITREV=${1:-$(git rev-parse --short HEAD)}
-# shorten long SHA1 from user to 7 characters
-GITREV=${GITREV:0:7}
-
-$DIR/archive "$GITREV"
-rpmbuild -ba --define "gitrev $GITREV" $DIR/*.spec
diff --git a/package/dnf-plugins-core.spec b/package/dnf-plugins-core.spec
deleted file mode 100644
index ac288a7..0000000
--- a/package/dnf-plugins-core.spec
+++ /dev/null
@@ -1,268 +0,0 @@
-%{!?gitrev: %global gitrev c8940d0}
-%{?!dnf_version: %global dnf_version 0.6.3}
-
-Name:		dnf-plugins-core
-Version:	0.1.5
-Release:	1%{?dist}
-Summary:	Core Plugins for DNF
-Group:		System Environment/Base
-License:	GPLv2+
-URL:		https://github.com/rpm-software-management/dnf-plugins-core
-
-# source archive is created by running package/archive from a git checkout
-Source0:	dnf-plugins-core-%{gitrev}.tar.xz
-
-BuildArch:	noarch
-BuildRequires:	cmake
-BuildRequires:	dnf = %{dnf_version}
-BuildRequires:	gettext
-BuildRequires:	pykickstart
-BuildRequires:	python-nose
-BuildRequires:	python-sphinx
-BuildRequires:	python2-devel
-Requires:	dnf = %{dnf_version}
-Requires:	pykickstart
-Requires:	python-requests
-
-%description
-Core Plugins for DNF. This package enhance DNF with builddep, copr,
-debuginfo-install, download, kickstart, needs-restarting, repoquery and
-reposync commands. Additionally provides generate_completion_cache, noroot and
-protected_packages passive plugins.
-
-%package -n python3-dnf-plugins-core
-Summary:	Core Plugins for DNF
-Group:		System Environment/Base
-BuildRequires:	python3-devel
-BuildRequires:	python3-dnf = %{dnf_version}
-BuildRequires:	python3-nose
-BuildRequires:	python3-sphinx
-Requires:	python3-dnf = %{dnf_version}
-
-%description -n python3-dnf-plugins-core
-Core Plugins for DNF, Python 3 version. This package enhance DNF with builddep, copr,
-debuginfo-install, download, kickstart, needs-restarting, repoquery and
-reposync commands. Additionally provides generate_completion_cache, noroot and
-protected_packages passive plugins.
-
-%prep
-%setup -q -n dnf-plugins-core
-rm -rf py3
-mkdir ../py3
-cp -a . ../py3/
-mv ../py3 ./
-
-%build
-%cmake .
-make %{?_smp_mflags}
-make doc-man
-pushd py3
-%cmake -DPYTHON_DESIRED:str=3 .
-make %{?_smp_mflags}
-make doc-man
-popd
-
-%install
-make install DESTDIR=$RPM_BUILD_ROOT
-%find_lang %{name}
-pushd py3
-make install DESTDIR=$RPM_BUILD_ROOT
-popd
-
-%check
-PYTHONPATH=./plugins /usr/bin/nosetests-2.* -s tests/
-PYTHONPATH=./plugins /usr/bin/nosetests-3.* -s tests/
-
-%files -f %{name}.lang
-%doc AUTHORS COPYING README.rst
-%dir %{_sysconfdir}/dnf/protected.d
-%ghost %{_var}/cache/dnf/packages.db
-%{python_sitelib}/dnf-plugins/*
-%{python_sitelib}/dnfpluginscore/
-%{_mandir}/man8/dnf.plugin.*
-
-%files -n python3-dnf-plugins-core -f %{name}.lang
-%doc AUTHORS COPYING README.rst
-%dir %{_sysconfdir}/dnf/protected.d
-%ghost %{_var}/cache/dnf/packages.db
-%exclude %{python3_sitelib}/dnf-plugins/__pycache__/
-%{python3_sitelib}/dnf-plugins/*
-%{python3_sitelib}/dnf-plugins/__pycache__/*
-%{python3_sitelib}/dnfpluginscore/
-%{_mandir}/man8/dnf.plugin.*
-
-%changelog
-
-* Fri Dec 5 2014 Jan Silhan <jsilhan@redhat.com> - 0.1.4-1
-- revert of commit 80ae3f4 (Jan Silhan)
-- transifex update (Jan Silhan)
-- spec: binded to current dnf version (Jan Silhan)
-- generate_completion_cache: use sqlite instead of text files (Igor Gnatenko)
-- logging: renamed log file (Related:RhBug:1074715) (Jan Silhan)
-- Add reposync. (RhBug:1139738) (Ales Kozumplik)
-- download: fix traceback if rpm package has no defined sourcerpm (RhBug: 1144003) (Tim Lauridsen)
-- lint: ignore warnings of a test accessing protected attribute. (Ales Kozumplik)
-- repoquery lint: logger is not used. (Ales Kozumplik)
-- repoquery: support querying of weak deps. (Ales Kozumplik)
-- needs_restarting: fix typo (Miroslav Suchý)
-- copr: migrate copr plugin form urlgrabber to python-request (Miroslav Suchý)
-- Add needs-restarting command. (Ales Kozumplik)
-
-* Thu Sep 4 2014 Jan Silhan <jsilhan@redhat.com> - 0.1.3-1
-- repoquery: output times in UTC. (Ales Kozumplik)
-- repoquery: missing help messages. (Ales Kozumplik)
-- repoquery: add --info. (RhBug:1135984) (Ales Kozumplik)
-- add Jan to AUTHORS. (Ales Kozumplik)
-- spec: extended package description with plugin names and commands (Related:RhBug:1132335) (Jan Silhan)
-- copr: check for 'ok' in 'output' for json data (RhBug:1134378) (Igor Gnatenko)
-- README: changed references to new repo location (Jan Silhan)
-- transifex update (Jan Silhan)
-- copr: convert key to unicode before guessing lenght (Miroslav Suchý)
-- Add pnemade to AUTHORS (Ales Kozumplik)
-- debuginfo-install: Use logger as module level variable and not instance attribute since dnf-0.6.0 release (RhBug:1130559) (Parag Nemade)
-- copr: Use logger as module level variable and not instance attribute since dnf-0.6.0 release (RhBug:1130559) (Parag Nemade)
-- copr: implement help command (Igor Gnatenko)
-- debuginfo-install: fix indenting (Igor Gnatenko)
-- debuginfo-install: use srpm basename for debuginfo (Igor Gnatenko)
-
-* Mon Jul 28 2014 Aleš Kozumplík <ales@redhat.com> - 0.1.2-1
-- BashCompletionCache: error strings are unicoded (RhBug:1118809) (Jan Silhan)
-- transifex update (Jan Silhan)
-- debuginfo-install: remove some pylint warnings (Igor Gnatenko)
-- debuginfo-install: fix installing when installed version not found in repos, optimize performance (RhBug: 1108321) (Ig
-- fix: copr plugin message for repo without builds (RhBug:1116389) (Adam Samalik)
-- logging: remove messages about initialization. (Ales Kozumplik)
-
-* Thu Jul 3 2014 Aleš Kozumplík <ales@redhat.com> - 0.1.1-2
-- packaging: add protected_packages.py to the package. (Ales Kozumplik)
-
-* Thu Jul 3 2014 Aleš Kozumplík <ales@redhat.com> - 0.1.1-1
-- protected_packages: prevent removal of the running kernel. (RhBug:1049310) (Ales Kozumplik)
-- packaging: create and own /etc/dnf/protected.d. (Ales Kozumplik)
-- doc: add documentation for protected_packages. (Ales Kozumplik)
-- doc: rename: generate-completion-cache -> generate_completion_cache. (Ales Kozumplik)
-- add protected_packages (RhBug:1111855) (Ales Kozumplik)
-- build: add python-requests to requires (RHBZ: 1104088) (Miroslav Suchý)
-- doc: typo: fix double 'plugin' in release notes. (Ales Kozumplik)
-
-* Wed Jun 4 2014 Aleš Kozumplík <ales@redhat.com> - 0.1.0-1
-- pylint: fix all pylint builddep problems. (Ales Kozumplik)
-- builddep: better error reporting on deps that actually don't exist. (Ales Kozumplik)
-- builddep: load available repos. (RhBug:1103906) (Ales Kozumplik)
-- tests: stop argparse from printing to stdout when tests run. (Ales Kozumplik)
-- packaging: all the manual pages with a glob. (Ales Kozumplik)
-- fix: packaging problem with query.py. (Ales Kozumplik)
-- doc: add reference documentation for repoquery. (Ales Kozumplik)
-- repoquery: support --provides, --requires etc. (Ales Kozumplik)
-- repoquery: make the CLI more compatible with Yum's repoquery. (Ales Kozumplik)
-- repoquery: some cleanups in the plugin and the tests. (Ales Kozumplik)
-- rename: query->repoquery. (RhBug:1045078) (Ales Kozumplik)
-- add pylint script for dnf-core-plugins. (Ales Kozumplik)
-- tests: repoquery: fix unit tests. (Ales Kozumplik)
-- add query tool (Tim Lauridsen)
-
-* Wed May 28 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.8-1
-- build: add sphinx to build requires. (Ales Kozumplik)
-- doc: packaging: add license block to each .rst. (Ales Kozumplik)
-- tests: stray print() in test_download.py. (Ales Kozumplik)
-- doc: put each synopsis on new line (Miroslav Suchý)
-- doc: cosmetic: project name in the documentation. (Ales Kozumplik)
-- doc: cleanups, form, style. (Ales Kozumplik)
-- doc: add documentation and man pages (Tim Lauridsen)
-- copr: remove repofile if failed to enable repo (Igor Gnatenko)
-- copr: honor -y and --assumeno (Miroslav Suchý)
-- py3: absolute imports and unicode literals everywhere. (Ales Kozumplik)
-- debuginfo-install: doesn't install latest pkgs (RhBug: 1096507) (Igor Gnatenko)
-- debuginfo-install: fix description (Igor Gnatenko)
-- debuginfo-install: fix logger debug messages (Igor Gnatenko)
-- build: install the download plugin (Tim Lauridsen)
-- download: update the download plugin with --source, --destdir & --resolve options (Tim Lauridsen)
-- Add a special ArgumentParser to parsing plugin cmd arguments and options (Tim Lauridsen)
-- tests: add __init__.py to make tests a module and use abs imports (Tim Lauridsen)
-- build: simplify plugins/CMakeLists.txt. (Ales Kozumplik)
-- dnf.cli.commands.err_mini_usage() changed name. (Ales Kozumplik)
-- kickstart: do not include kickstart errors into own messages. (Radek Holy)
-
-* Wed Apr 23 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.7-1
-- build: gettext is also needed as a buildreq (Tim Lauridsen)
-- copr: use usage & summary class attributes, to work with dnf 0.5.0 use shared lib dnfpluginscore for translation wrapp
-- build: add cmake as buildreq (Tim Lauridsen)
-- generate-completion-cache: fix shared lib name (Tim Lauridsen)
-- make .spec use gitrev in the source file add helper script for building source archive (Tim Lauridsen)
-- Added transifex config (Tim Lauridsen)
-- tests: use cli logger in kickstart test (Tim Lauridsen)
-- Added translation .pot file Added da translation files so we have something to build & install (Tim Lauridsen)
-- Added CMake files Added CMake build to .spec & and added translation files handling (Tim Lauridsen)
-- make plugins use shared lib added translation wrappers added missing usage & summary PEP8 fixes (Tim Lauridsen)
-- added shared dnfpluginscore lib (Tim Lauridsen)
-- copr: C:139, 0: Unnecessary parens after 'print' keyword (superfluous-parens) (Miroslav Suchý)
-- copr: W: 23, 0: Unused import gettext (unused-import) (Miroslav Suchý)
-- copr: C: 33, 0: No space allowed before : (Miroslav Suchý)
-- copr: some python3 migration (Miroslav Suchý)
-- copr: get rid of dnf i18n imports (Miroslav Suchý)
-- remove dnf.yum.i18n imports. (Ales Kozumplik)
-- copr: Fix the playground upgrade command. (Tadej Janež)
-- copr: implement search function (Igor Gnatenko)
-- better format output (Miroslav Suchý)
-- implement playground plugin (Miroslav Suchý)
-- move removing of repo into method (Miroslav Suchý)
-- check root only for actions which really need root (Miroslav Suchý)
-- move repo downloading into separate method (Miroslav Suchý)
-- define copr url as class attribute (Miroslav Suchý)
-- better wording of warning (Miroslav Suchý)
-- move question to function argument (Miroslav Suchý)
-- move guessing chroot into function (Miroslav Suchý)
-- copr: use common lib use Command.usage & summary cleanup imports & PEP8 fixes (Tim Lauridsen)
-- builddep: added usage & summary & fix some PEP8 issues (Tim Lauridsen)
-- kickstart: use new public Command.usage & Command.summary api (Tim Lauridsen)
-- fix resource leak in builddep.py. (Ales Kozumplik)
-- refactor: command plugins use demands mechanism. (Ales Kozumplik)
-- noroot: move to the new 'demands' mechanism to check the need of root. (Ales Kozumplik)
-- tests: fix locale independence. (Radek Holy)
-- [copr] correctly specify chroot when it should be guessed (Miroslav Suchý)
-
-* Mon Mar 17 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.6-1
-- clenaup: remove commented out code (Miroslav Suchý)
-- copr: list: print description (Igor Gnatenko)
-- builddep: rpm error messages sink. (Ales Kozumplik)
-- builddep: improve error handling on an command argument (RhBug:1074436) (Ales Kozumplik)
-- copr: handling case when no argument is passed on cli (Miroslav Suchý)
-- copr: delete excess argument (Igor Gnatenko)
-- add copr plugin (Miroslav Suchý)
-- debuginfo-install: check for root with dnf api (Igor Gnatenko)
-- packaging: fix bogus dates. (Ales Kozumplik)
-
-* Wed Feb 26 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.5-2
-- packaging: add debuginfo-install.py (Ales Kozumplik)
-
-* Wed Feb 26 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.5-1
-- packaging: add builddep.py to the RPM. (Ales Kozumplik)
-
-* Tue Feb 25 2014 Radek Holý <rholy@redhat.com> - 0.0.4-1
-- refactor: use Base.install instead of installPkgs in kickstart plugin. (Radek Holy)
-- refactor: move kickstart arguments parsing to standalone method. (Radek Holy)
-- tests: test effects instead of mock calls. (Radek Holy)
-- Add debuginfo-install plugin. (RhBug:1045770) (Igor Gnatenko)
-- builddep: needs to be run under root. (RhBug:1065851) (Ales Kozumplik)
-
-* Thu Feb 6 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.3-1
-- tests: import mock through support so its simpler for the test cases. (Ales Kozumplik)
-- packaging: fix typos in the spec. (Ales Kozumplik)
-- [completion_cache] Cache installed packages, update the cache less frequently (Elad Alfassa)
-- Add bash completion to dnf (Elad Alfassa)
-- packaging: missing buildrequire (Ales Kozumplik)
-
-* Mon Jan 13 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.2-1
-- First release.
-
-* Wed Jan 8 2014 Cristian Ciupitu <cristian.ciupitu@yahoo.com> - 0.0.1-4
-- Spec updates.
-
-* Tue Jan 7 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.1-3
-- Spec updates.
-
-* Mon Jan 6 2014 Aleš Kozumplík <ales@redhat.com> - 0.0.1-2
-- Spec updates.
-
-* Fri Dec 20 2013 Aleš Kozumplík <ales@redhat.com> - 0.0.1-1
-- The initial package version.
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index a21777b..1f40737 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -1,5 +1,6 @@
 INSTALL (FILES builddep.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
 INSTALL (FILES debuginfo-install.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
+INSTALL (FILES config_manager.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
 INSTALL (FILES copr.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
 INSTALL (FILES download.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
 INSTALL (FILES generate_completion_cache.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
diff --git a/plugins/config_manager.py b/plugins/config_manager.py
new file mode 100644
index 0000000..812e34a
--- /dev/null
+++ b/plugins/config_manager.py
@@ -0,0 +1,255 @@
+#
+# Copyright (C) 2015  Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+# Public License for more details.  You should have received a copy of the
+# GNU General Public License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from dnfpluginscore import _, logger
+
+import dnf
+import dnf.cli
+import dnf.pycomp
+import dnfpluginscore
+import dnfpluginscore.lib
+import os
+import re
+import shutil
+
+
+class ConfigManager(dnf.Plugin):
+
+    name = 'config-manager'
+
+    def __init__(self, base, cli):
+        super(ConfigManager, self).__init__(base, cli)
+        self.base = base
+        self.cli = cli
+        if self.cli is not None:
+            self.cli.register_command(ConfigManagerCommand)
+
+
+class ConfigManagerCommand(dnf.cli.Command):
+
+    aliases = ['config-manager']
+    summary = _('manage dnf configuration options and repositories')
+    usage = '[%s] [%s]' % (_('OPTIONS'), _('KEYWORDS'))
+
+    def __init__(self, cli):
+        super(ConfigManagerCommand, self).__init__(cli)
+        self.opts = None
+        self.parser = None
+
+    def configure(self, args):
+        # setup sack and populate it with enabled repos
+        demands = self.cli.demands
+        demands.available_repos = True
+
+        self.parser = dnfpluginscore.ArgumentParser(self.aliases[0])
+        self.parser.add_argument(
+            'repo', nargs='*',
+            help=_('repo to modify'))
+        self.parser.add_argument(
+            '--save', default=False, action='store_true',
+            help=_('save the current options (useful with --setopt)'))
+        self.parser.add_argument(
+            '--set-enabled', default=False, action='store_true',
+            help=_('enable the specified repos (automatically saves)'))
+        self.parser.add_argument(
+            '--set-disabled', default=False, action='store_true',
+            help=_('disable the specified repos (automatically saves)'))
+        self.parser.add_argument(
+            '--add-repo', default=[], action='append', metavar='URL',
+            help=_('add (and enable) the repo from the specified file or url'))
+        self.parser.add_argument(
+            '--dump', default=False, action='store_true',
+            help=_('print current configuration values to stdout'))
+
+        self.opts = self.parser.parse_args(args)
+
+        if self.opts.help_cmd:
+            print(self.parser.format_help())
+            return
+
+        if (self.opts.save or self.opts.set_enabled or
+                self.opts.set_disabled or self.opts.add_repo):
+            demands.root_user = True
+
+
+    def run(self, _args):
+        """Execute the util action here."""
+
+        if self.opts.help_cmd:
+            return
+        if self.opts.set_enabled and self.opts.set_disabled:
+            logger.error(
+                _("Error: Trying to enable and disable repos at the same time."))
+            self.opts.set_enabled = self.opts.set_disabled = False
+        if self.opts.set_enabled and not self.opts.repo:
+            logger.error(_("Error: Trying to enable already enabled repos."))
+            self.opts.set_enabled = False
+
+        if self.opts.add_repo:
+            self.add_repo()
+        else:
+            self.modify_repo()
+
+    def modify_repo(self):
+        """ process --set-enabled, --set-disabled and --setopt options """
+
+        sbc = self.base.conf
+        modify = []
+        if hasattr(self.cli, 'main_setopts') and self.cli.main_setopts:
+            modify = self.cli.main_setopts.items
+        if not self.opts.repo or 'main' in self.opts.repo:
+            if self.opts.dump:
+                print(self.base.output.fmtSection('main'))
+                print(self.base.conf.dump())
+            if self.opts.save and modify:
+                # modify [main] in dnf.conf
+                dnfpluginscore.lib.write_raw_configfile(dnf.const.CONF_FILENAME,
+                                                        'main', sbc.substitutions,
+                                                        sbc.cfg.options,
+                                                        sbc.iteritems,
+                                                        sbc.optionobj,
+                                                        modify)
+
+        if self.opts.set_enabled or self.opts.set_disabled:
+            self.opts.save = True
+            modify.append('enabled')
+
+        if self.opts.repo:
+            matched = []
+            for name in self.opts.repo:
+                matched.extend(self.base.repos.get_matching(name))
+        else:
+            matched = self.base.repos.iter_enabled()
+
+        if not matched:
+            raise dnf.exceptions.Error(_("No matching repo to modify: %s.")
+                                       % ', '.join(self.opts.repo))
+        for repo in sorted(matched):
+            if self.opts.dump:
+                print(self.base.output.fmtSection('repo: ' + repo.id))
+            if self.opts.set_enabled and not repo.enabled:
+                repo.enable()
+            elif self.opts.set_disabled and repo.enabled:
+                repo.disable()
+            if self.opts.dump:
+                print(repo.dump())
+            repo_modify = modify[:]
+            if (hasattr(self.cli, 'repo_setopts')
+                    and repo.id in self.cli.repo_setopts):
+                repo_modify.extend(self.cli.repo_setopts[repo.id].items)
+            if self.opts.save and modify:
+                dnfpluginscore.lib.write_raw_configfile(repo.repofile,
+                                                        repo.id,
+                                                        sbc.substitutions,
+                                                        repo.cfg.options,
+                                                        repo.iteritems,
+                                                        repo.optionobj,
+                                                        repo_modify)
+
+    def add_repo(self):
+        """ process --add-repo option """
+
+        # put repo file into first reposdir which exists or create it
+        myrepodir = None
+        for rdir in self.base.conf.reposdir:
+            if os.path.exists(rdir):
+                myrepodir = rdir
+                break
+
+        if not myrepodir:
+            myrepodir = self.base.conf.reposdir[0]
+            dnf.util.ensure_dir(myrepodir)
+
+        for url in self.opts.add_repo:
+            if dnf.pycomp.urlparse.urlparse(url).scheme == '':
+                url = 'file://' + os.path.abspath(url)
+            logger.info(_('Adding repo from: %s'), url)
+            if url.endswith('.repo'):
+                # .repo file - download, put into reposdir and enable it
+                destname = os.path.basename(url)
+                destname = os.path.join(myrepodir, destname)
+                try:
+                    f = dnfpluginscore.lib.urlopen(self, None, url, 'w+')
+                    shutil.copy2(f.name, destname)
+                    f.close()
+                except IOError as e:
+                    logger.error(e)
+                    continue
+            else:
+                # just url to repo, create .repo file on our own
+                repoid = sanitize_url_to_fs(url)
+                reponame = 'created by dnf config-manager from %s' % url
+                destname = os.path.join(myrepodir, "%s.repo" % repoid)
+                content = "[%s]\nname=%s\nbaseurl=%s\nenabled=1\n" % \
+                                                (repoid, reponame, url)
+                if not save_to_file(destname, content):
+                    continue
+
+
+def save_to_file(filename, content):
+    try:
+        with open(filename, 'w+') as fd:
+            dnf.pycomp.write_to_file(fd, content)
+    except (IOError, OSError) as e:
+        logger.error(_('Could not save repo to repofile %s: %s'),
+                     filename, e)
+        return False
+    return True
+
+# Regular expressions to sanitise cache filenames
+RE_SCHEME = re.compile(r'^\w+:/*(\w+:|www\.)?')
+RE_SLASH = re.compile(r'[?/:&#|]+')
+RE_BEGIN = re.compile(r'^[,.]*')
+RE_FINAL = re.compile(r'[,.]*$')
+
+def sanitize_url_to_fs(url):
+    """Return a filename suitable for the filesystem
+
+    Strips dangerous and common characters to create a filename we
+    can use to store the cache in.
+    """
+
+    try:
+        if RE_SCHEME.match(url):
+            if dnf.pycomp.PY3:
+                url = url.encode('idna').decode('utf-8')
+            else:
+                if isinstance(url, str):
+                    url = url.decode('utf-8').encode('idna')
+                else:
+                    url = url.encode('idna')
+                if isinstance(url, unicode):
+                    url = url.encode('utf-8')
+    except (UnicodeDecodeError, UnicodeEncodeError, UnicodeError, TypeError):
+        pass
+    url = RE_SCHEME.sub("", url)
+    url = RE_SLASH.sub("_", url)
+    url = RE_BEGIN.sub("", url)
+    url = RE_FINAL.sub("", url)
+
+    # limit length of url
+    if len(url) > 250:
+        parts = url[:185].split('_')
+        lastindex = 185-len(parts[-1])
+        csum = dnf.yum.misc.Checksums(['sha256'])
+        csum.update(url[lastindex:])
+        url = url[:lastindex] + '_' + csum.hexdigest()
+
+    return url
diff --git a/plugins/copr.py b/plugins/copr.py
index 3a91ce6..8295855 100644
--- a/plugins/copr.py
+++ b/plugins/copr.py
@@ -1,6 +1,6 @@
 # supplies the 'copr' command.
 #
-# Copyright (C) 2014  Red Hat, Inc.
+# Copyright (C) 2014-2015  Red Hat, Inc.
 #
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -20,6 +20,7 @@
 from __future__ import print_function
 from dnfpluginscore import _, logger
 from dnf.i18n import ucd
+import dnfpluginscore.lib
 
 import dnf
 import glob
@@ -30,9 +31,14 @@ import requests
 import urllib
 
 
-yes = set([_('yes'), _('y')])
-no = set([_('no'), _('n'), ''])
+YES = set([_('yes'), _('y')])
+NO = set([_('no'), _('n'), ''])
 
+# compatibility with Py2 and Py3 - rename raw_input() to input() on Py2
+try:
+    input = raw_input
+except NameError:
+    pass
 
 class Copr(dnf.Plugin):
     """DNF plugin supplying the 'copr' command."""
@@ -118,8 +124,7 @@ Do you want to continue? [y/N]: """)
             #http://copr.fedoraproject.org/api/coprs/ignatenkobrain/
             api_path = "/api/coprs/{}/".format(project_name)
 
-            opener = urllib.FancyURLopener({})
-            res = opener.open(self.copr_url + api_path)
+            res = dnfpluginscore.lib.urlopen(self, None, self.copr_url + api_path, 'w+')
             try:
                 json_parse = json.loads(res.read())
             except ValueError:
@@ -132,7 +137,7 @@ Do you want to continue? [y/N]: """)
             i = 0
             while i < len(json_parse["repos"]):
                 msg = "{0}/{1} : ".format(project_name,
-                      json_parse["repos"][i]["name"])
+                                          json_parse["repos"][i]["name"])
                 desc = json_parse["repos"][i]["description"]
                 if not desc:
                     desc = _("No description given")
@@ -143,18 +148,19 @@ Do you want to continue? [y/N]: """)
             #http://copr.fedoraproject.org/api/coprs/search/tests/
             api_path = "/api/coprs/search/{}/".format(project_name)
 
-            opener = urllib.FancyURLopener({})
-            res = opener.open(self.copr_url + api_path)
+            res = dnfpluginscore.lib.urlopen(self, None, self.copr_url + api_path, 'w+')
             try:
                 json_parse = json.loads(res.read())
             except ValueError:
-                raise dnf.exceptions.Error(_("Can't parse search for '{}'.").format(project_name))
+                raise dnf.exceptions.Error(_("Can't parse search for '{}'."
+                                            ).format(project_name))
             self._check_json_output(json_parse)
             section_text = _("Matched: {}").format(project_name)
             self._print_match_section(section_text)
             i = 0
             while i < len(json_parse["repos"]):
-                msg = "{0}/{1} : ".format(json_parse["repos"][i]["username"], json_parse["repos"][i]["coprname"])
+                msg = "{0}/{1} : ".format(json_parse["repos"][i]["username"],
+                                          json_parse["repos"][i]["coprname"])
                 desc = json_parse["repos"][i]["description"]
                 if not desc:
                     desc = _("No description given.")
@@ -175,12 +181,12 @@ Do you want to continue? [y/N]: """)
         elif self.base.conf.assumeno and not self.base.conf.assumeyes:
             raise dnf.exceptions.Error(_('Safe and good answer. Exiting.'))
 
-        answer = raw_input(question).lower()
+        answer = input(question).lower()
         answer = _(answer)
-        while not ((answer in yes) or (answer in no)):
-            answer = raw_input(question).lower()
+        while not ((answer in YES) or (answer in NO)):
+            answer = input(question).lower()
             answer = _(answer)
-        if answer in yes:
+        if answer in YES:
             return
         else:
             raise dnf.exceptions.Error(_('Safe and good answer. Exiting.'))
@@ -256,9 +262,9 @@ Do you want to continue? [y/N]: """)
         return output
 
     @classmethod
-    def _check_json_output(cls, json):
-        if json["output"] != "ok":
-            raise dnf.exceptions.Error("{}".format(json["error"]))
+    def _check_json_output(cls, json_obj):
+        if json_obj["output"] != "ok":
+            raise dnf.exceptions.Error("{}".format(json_obj["error"]))
 
 
 class Playground(dnf.Plugin):
@@ -294,7 +300,7 @@ Do you want to continue? [y/N]: """)
             raise dnf.cli.CliError(_("Unknown response from server."))
         for repo in output["repos"]:
             project_name = "{0}/{1}".format(repo["username"],
-                repo["coprname"])
+                                            repo["coprname"])
             repo_filename = "/etc/yum.repos.d/_playground_{}.repo" \
                     .format(project_name.replace("/", "-"))
             try:
@@ -304,7 +310,8 @@ Do you want to continue? [y/N]: """)
                     self.copr_url, project_name, chroot)
                 req = requests.get(api_url)
                 output2 = self._get_data(req)
-                if output2 and ("output" in output2) and (output2["output"] == "ok"):
+                if (output2 and ("output" in output2)
+                        and (output2["output"] == "ok")):
                     self._download_repo(project_name, repo_filename, chroot)
             except dnf.exceptions.Error:
                 # likely 404 and that repo does not exist
diff --git a/plugins/debuginfo-install.py b/plugins/debuginfo-install.py
index 194b24c..4783ed2 100644
--- a/plugins/debuginfo-install.py
+++ b/plugins/debuginfo-install.py
@@ -23,6 +23,7 @@ from dnfpluginscore import _, logger
 
 import dnf
 import dnf.cli
+import dnf.subject
 
 
 class DebuginfoInstall(dnf.Plugin):
@@ -44,8 +45,8 @@ class DebuginfoInstallCommand(dnf.cli.Command):
     summary = _('install debuginfo packages')
     usage = "[PACKAGE...]"
 
-    done = []
-    rejected = []
+    srcdone = []
+    reqdone = []
     packages = None
     packages_available = None
     packages_installed = None
@@ -62,12 +63,11 @@ class DebuginfoInstallCommand(dnf.cli.Command):
         self.packages = self.base.sack.query()
         self.packages_available = self.packages.available()
         self.packages_installed = self.packages.installed()
-        for pkg in args:
-            pkgs = self.packages_installed.filter(name=pkg)
-            if not pkgs:
-                pkgs = self.packages_available.filter(name=pkg)
-            for pkg in pkgs:
-                self._di_install(pkg, None)
+
+        for pkgspec in args:
+            for pkg in dnf.subject.Subject(pkgspec).get_best_query(
+                    self.cli.base.sack):
+                self._di_install(pkg)
 
     @staticmethod
     def _pkgname_src(package):
@@ -84,86 +84,53 @@ class DebuginfoInstallCommand(dnf.cli.Command):
         assert "-debuginfo" not in srcname
         return "{}-debuginfo".format(srcname)
 
-    def _is_available(self, package, match_evra):
+    def _dbg_available(self, package, match_evra):
         dbgname = self._pkgname_dbg(package)
         if match_evra:
-            avail = self.packages_available.filter(
+            return self.packages_available.filter(
                 name="{}".format(dbgname),
                 epoch=int(package.epoch),
                 version=str(package.version),
                 release=str(package.release),
                 arch=str(package.arch))
         else:
-            avail = self.packages_available.filter(
+            return self.packages_available.filter(
                 name="{}".format(dbgname),
                 arch=str(package.arch))
-        if len(avail) != 0:
-            srcname = self._pkgname_src(package)
-            if match_evra:
-                return self.packages_available.filter(
-                    name="{}".format(srcname),
-                    epoch=int(package.epoch),
-                    version=str(package.version),
-                    release=str(package.release),
-                    arch=str(package.arch))
-            else:
-                return self.packages_available.filter(
-                    name="{}".format(srcname),
-                    arch=str(package.arch))
-        else:
-            return False
 
-    def _di_install(self, package, require):
+    def _di_install(self, package):
         srcname = self._pkgname_src(package)
         dbgname = self._pkgname_dbg(package)
-        if srcname in self.done \
-                or require in self.done \
-                or package in self.rejected:
-            return
-        if self._is_available(package, True):
-            self.done.append(srcname)
-            if require:
-                self.done.append(require)
-            di = "{0}-{1}:{2}-{3}.{4}".format(
-                dbgname,
-                package.epoch,
-                package.version,
-                package.release,
-                package.arch)
-            self.base.install(di)
-        else:
-            if self._is_available(package, False):
+        if not srcname in self.srcdone:
+            if self._dbg_available(package, True):
+                di = "{0}-{1}:{2}-{3}.{4}".format(
+                    dbgname,
+                    package.epoch,
+                    package.version,
+                    package.release,
+                    package.arch)
+                self.base.install(di)
+            elif self._dbg_available(package, False):
                 di = "{0}.{1}".format(dbgname, package.arch)
                 self.base.install(di)
-                self.done.append(srcname)
-                if require:
-                    self.done.append(require)
-            else:
-                pass
+            self.srcdone.append(srcname)
+
+        if package.name in self.reqdone:
+            return
+        self.reqdone.append(package.name)
         for req in package.requires:
             if str(req).startswith("rpmlib("):
                 continue
-            elif str(req) in self.done:
+            elif str(req) in self.reqdone:
                 continue
             elif str(req).find(".so") != -1:
                 provides = self.packages_available.filter(provides=req)
                 for p in provides:
-                    if str(p.name) in self.done or p in self.rejected:
+                    if p.name in self.reqdone:
                         continue
                     pkgs = self.packages_installed.filter(name=p.name)
-                    if len(pkgs) != 0:
-                        pkgs_avail = self._is_available(pkgs[0], True)
-                        if not pkgs_avail:
-                            for x in pkgs:
-                                logger.debug(
-_("Can't find debuginfo package for: {0}-{1}:{2}-{3}.{4}").format(
-    x.name, x.epoch, x.version, x.release, x.arch))
-                                self.rejected.append(x)
-                            pkgs = []
-                        else:
-                            pkgs = pkgs_avail
-                    for pkg in pkgs:
-                        self._di_install(pkg, str(req))
+                    for dep in pkgs:
+                        self._di_install(dep)
 
     def _enable_debug_repos(self):
         repos = {}
diff --git a/plugins/dnfpluginscore/lib.py b/plugins/dnfpluginscore/lib.py
new file mode 100644
index 0000000..144d126
--- /dev/null
+++ b/plugins/dnfpluginscore/lib.py
@@ -0,0 +1,121 @@
+#
+# Copyright (C) 2015  Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+# Public License for more details.  You should have received a copy of the
+# GNU General Public License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+
+from dnf.pycomp import PY3
+
+import dnf
+import iniparse
+import librepo
+import tempfile
+
+
+def current_value(plugin, repo, option):
+    """
+    Returns current value of the option
+    (set in .repo or dnf.conf or on commandline).
+    """
+    if (repo is not None and
+            hasattr(repo, option) and getattr(repo, option) is not None):
+        return getattr(repo, option)
+    conf = plugin.base.conf
+    if (hasattr(conf, option) and getattr(conf, option) is not None):
+        return getattr(conf, option)
+    return None
+
+
+def urlopen(plugin, repo, url, mode='w+b', **kwargs):
+    """ modified dnf.util.urlopen() which respects proxy setting
+        even for non-repo downloads
+    """
+    conf = plugin.base.conf
+
+    def non_repo_handle():
+        handle = librepo.Handle()
+        handle.useragent = dnf.const.USER_AGENT
+        # see dnf.repo.Repo._handle_new_remote() how to pass
+        handle.maxspeed = conf.throttle if type(conf.throttle) is int \
+                     else int(conf.bandwidth * conf.throttle)
+        handle.proxy = conf.proxy
+        handle.proxyuserpwd = dnf.repo._user_pass_str(conf.proxy_username,
+                                                      conf.proxy_password)
+        handle.sslverifypeer = handle.sslverifyhost = conf.sslverify
+        return handle
+
+    if PY3 and 'b' not in mode:
+        kwargs.setdefault('encoding', 'utf-8')
+    fo = tempfile.NamedTemporaryFile(mode, **kwargs)
+    if repo:
+        handle = repo.get_handle()
+    else:
+        handle = non_repo_handle()
+    try:
+        librepo.download_url(url, fo.fileno(), handle)
+    except librepo.LibrepoException as e:
+        raise IOError(e.args[1])
+    fo.seek(0)
+    return fo
+
+
+def write_raw_configfile(filename, section_id, substitutions,
+                         cfgoptions, items, optionobj,
+                         modify=None):
+    """
+    Code adopted from yum-config-manager writeRawRepoFile().
+    filename   - name of config file (.conf or .repo)
+    section_id - id of modified section (e.g. main, fedora, updates)
+    substitutions - instance of base.conf.substitutions
+    cfgoptions - options parsed from conf file (e.g. base.conf.cfg.options)
+    items      - current global or repo settings (e.g. base.conf.iteritems)
+    optionobj  - option parse object (e.g. base.conf.optionobj)
+    modify     - list of modified options
+    """
+    ini = iniparse.INIConfig(open(filename))
+
+    osection_id = section_id
+    # b/c repoids can have $values in them we need to map both ways to figure
+    # out which one is which
+    if section_id not in ini:
+        for sect in ini:
+            if dnf.conf.parser.substitute(sect, substitutions) == section_id:
+                section_id = sect
+
+    # Updated the ConfigParser with the changed values
+    cfgopts = cfgoptions(osection_id)
+    for name, value in items():
+        if value is None: # Proxy
+            continue
+
+        if modify is not None and name not in modify:
+            continue
+
+        option = optionobj(name)
+        ovalue = option.tostring(value)
+        #  If the value is the same, but just interpreted ... when we don't want
+        # to keep the interpreted values.
+        if (name in ini[section_id] and
+                ovalue == dnf.conf.parser.substitute(ini[section_id][name],
+                                                     substitutions)):
+            ovalue = ini[section_id][name]
+
+        if name not in cfgopts and option.default == value:
+            continue
+
+        ini[section_id][name] = ovalue
+    fp = open(filename, "w")
+    fp.write(str(ini))
+    fp.close()
diff --git a/plugins/download.py b/plugins/download.py
index cbb6e4e..7e8bf41 100644
--- a/plugins/download.py
+++ b/plugins/download.py
@@ -1,6 +1,6 @@
 # download.py, supplies the 'download' command.
 #
-# Copyright (C) 2013-2014  Red Hat, Inc.
+# Copyright (C) 2013-2015  Red Hat, Inc.
 #
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -27,7 +27,6 @@ import dnf.exceptions
 import dnf.i18n
 import dnf.subject
 import dnfpluginscore
-import functools
 import hawkey
 import itertools
 import os
@@ -39,6 +38,7 @@ class Download(dnf.Plugin):
     name = 'download'
 
     def __init__(self, base, cli):
+        super(Download, self).__init__(base, cli)
         self.base = base
         self.cli = cli
         if self.cli is not None:
@@ -51,6 +51,11 @@ class DownloadCommand(dnf.cli.Command):
     summary = _('Download package to current directory')
     usage = _('PACKAGE...')
 
+    def __init__(self, cli):
+        super(DownloadCommand, self).__init__(cli)
+        self.opts = None
+        self.parser = None
+
     def configure(self, args):
         # setup sack and populate it with enabled repos
         demands = self.cli.demands
@@ -64,13 +69,15 @@ class DownloadCommand(dnf.cli.Command):
         # You must only add options not used by dnf already
         self.parser = dnfpluginscore.ArgumentParser(self.aliases[0])
         self.parser.add_argument('packages', nargs='+',
-                            help=_('packages to download'))
+                                 help=_('packages to download'))
         self.parser.add_argument("--source", action='store_true',
-                            help=_('download the src.rpm instead'))
-        self.parser.add_argument('--destdir',
-                            help=_('download path, default is current dir'))
-        self.parser.add_argument('--resolve', action='store_true',
-                            help=_('resolve and download needed dependencies'))
+                                 help=_('download the src.rpm instead'))
+        self.parser.add_argument(
+            '--destdir',
+            help=_('download path, default is current dir'))
+        self.parser.add_argument(
+            '--resolve', action='store_true',
+            help=_('resolve and download needed dependencies'))
 
         # parse the options/args
         # list available options/args on errors & exit
@@ -91,8 +98,7 @@ class DownloadCommand(dnf.cli.Command):
         else:
             dest = dnf.i18n.ucd(os.getcwd())
 
-        move = functools.partial(self._move_package, dest)
-        map(move, locations)
+        self._move_packages(dest, locations)
 
     def _download_rpms(self, pkg_specs):
         """Download packages to dnf cache."""
@@ -137,7 +143,8 @@ class DownloadCommand(dnf.cli.Command):
             logger.debug(_('Error in resolve'))
             return []
 
-    def _get_source_packages(self, pkgs):
+    @staticmethod
+    def _get_source_packages(pkgs):
         """Get list of source rpm names for a list of packages."""
         source_pkgs = set()
         for pkg in pkgs:
@@ -145,31 +152,27 @@ class DownloadCommand(dnf.cli.Command):
                 source_pkgs.add(pkg.sourcerpm)
                 logger.debug('  --> Package : %s Source : %s',
                              str(pkg), pkg.sourcerpm)
+            elif pkg.arch == 'src':
+                source_pkgs.add("%s-%s.src.rpm" % (pkg.name, pkg.evr))
             else:
-                logger.info(_("No source rpm definded for %s"), str(pkg))
+                logger.info(_("No source rpm defined for %s"), str(pkg))
         return list(source_pkgs)
 
     def _enable_source_repos(self):
         """Enable source repositories for enabled binary repositories.
 
-        binary repositories will be disabled and the dnf sack will be reloaded
+        Don't disable the binary ones because they can contain SRPMs as well
+        (this applies to COPR and to user-managed repos).
+        The dnf sack will be reloaded.
         """
-        repo_dict = {}
-        # find the source repos for the enabled binary repos
+        # enable the source repos
         for repo in self.base.repos.iter_enabled():
-            source_repo = '%s-source' % repo.id
-            if source_repo in self.base.repos:
-                repo_dict[repo.id] = (repo, self.base.repos[source_repo])
-            else:
-                repo_dict[repo.id] = (repo, None)
-        # disable the binary & enable the source ones
-        for id_ in repo_dict:
-            repo, src_repo = repo_dict[id_]
-            repo.disable()
-            if src_repo:
-                logger.info(_('enabled %s repository') % src_repo.id)
-                src_repo.enable()
-       # reload the sack
+            source_repo_id = '%s-source' % repo.id
+            if source_repo_id in self.base.repos:
+                source_repo = self.base.repos[source_repo_id]
+                logger.info(_('enabled %s repository'), source_repo.id)
+                source_repo.enable()
+        # reload the sack
         self.base.fill_sack()
 
     def _get_query(self, pkg_spec):
@@ -191,8 +194,12 @@ class DownloadCommand(dnf.cli.Command):
                      release=nevra.release, arch=nevra.arch)
         return q
 
-    def _move_package(self, target, location):
+    @staticmethod
+    def _move_packages(target, locations):
         """Move the downloaded package to target."""
-        shutil.copy(location, target)
-        os.unlink(location)
+        if not os.path.exists(target):
+            os.makedirs(target)
+        for pkg in locations:
+            shutil.copy(pkg, target)
+            os.unlink(pkg)
         return target
diff --git a/plugins/generate_completion_cache.py b/plugins/generate_completion_cache.py
index 7fbb47a..b9df242 100644
--- a/plugins/generate_completion_cache.py
+++ b/plugins/generate_completion_cache.py
@@ -2,6 +2,7 @@
 # generate_completion_cache.py - generate cache for dnf bash completion
 # Copyright © 2013 Elad Alfassa <elad@fedoraproject.org>
 # Copyright (C) 2014 Igor Gnatenko <i.gnatenko.brain@gmail.com>
+# Copyright (C) 2015  Red Hat, Inc.
 
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -28,6 +29,7 @@ class BashCompletionCache(dnf.Plugin):
     name = 'generate_completion_cache'
 
     def __init__(self, base, cli):
+        super(BashCompletionCache, self).__init__(base, cli)
         self.base = base
         self.cache_file = "/var/cache/dnf/packages.db"
 
@@ -60,7 +62,7 @@ class BashCompletionCache(dnf.Plugin):
                     cur.execute("delete from available")
                     avail_pkgs = self.base.sack.query().available()
                     avail_pkgs_insert = [["{}.{}".format(x.name, x.arch)]
-                        for x in avail_pkgs if x.arch != "src"]
+                                         for x in avail_pkgs if x.arch != "src"]
                     cur.executemany("insert or ignore into available values (?)",
                                     avail_pkgs_insert)
                     conn.commit()
@@ -80,7 +82,7 @@ class BashCompletionCache(dnf.Plugin):
                 cur.execute("delete from installed")
                 inst_pkgs = self.base.sack.query().installed()
                 inst_pkgs_insert = [["{}.{}".format(x.name, x.arch)]
-                    for x in inst_pkgs if x.arch != "src"]
+                                    for x in inst_pkgs if x.arch != "src"]
                 cur.executemany("insert or ignore into installed values (?)",
                                 inst_pkgs_insert)
                 conn.commit()
diff --git a/plugins/ghost.py b/plugins/ghost.py
index 1cff03c..ac60780 100644
--- a/plugins/ghost.py
+++ b/plugins/ghost.py
@@ -1,6 +1,6 @@
 # ghost.py, it's a show about nothing.
 #
-# Copyright (C) 2013  Red Hat, Inc.
+# Copyright (C) 2013-2015  Red Hat, Inc.
 #
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -29,6 +29,7 @@ class Ghost(dnf.Plugin):
     name = 'ghost'
 
     def __init__(self, base, cli):
+        super(Ghost, self).__init__(base, cli)
         self.base = base
         self.cli = cli
         if cli is None:
@@ -36,7 +37,8 @@ class Ghost(dnf.Plugin):
         else:
             self._out('loaded (with CLI)')
 
-    def _out(self, msg):
+    @staticmethod
+    def _out(msg):
         logger.debug('Ghost plugin: %s', msg)
 
     def config(self):
diff --git a/plugins/noroot.py b/plugins/noroot.py
index 014b226..f82b6a3 100644
--- a/plugins/noroot.py
+++ b/plugins/noroot.py
@@ -1,6 +1,6 @@
 # noroot.py
 #
-# Copyright (C) 2013  Red Hat, Inc.
+# Copyright (C) 2013-2015  Red Hat, Inc.
 #
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -19,7 +19,7 @@
 
 from __future__ import absolute_import
 from __future__ import unicode_literals
-from dnfpluginscore import logger, _
+from dnfpluginscore import _
 
 import dnf
 import dnf.exceptions
@@ -33,6 +33,7 @@ class Noroot(dnf.Plugin):
     name = 'noroot'
 
     def __init__(self, base, cli):
+        super(Noroot, self).__init__(base, cli)
         self.base = base
         self.cli = cli
 
diff --git a/plugins/protected_packages.py b/plugins/protected_packages.py
index b202011..a241eae 100644
--- a/plugins/protected_packages.py
+++ b/plugins/protected_packages.py
@@ -1,7 +1,7 @@
 # protected_packages.py
 # Prevent package removals that could leave the system broken.
 #
-# Copyright (C) 2014  Red Hat, Inc.
+# Copyright (C) 2014-2015  Red Hat, Inc.
 #
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -20,7 +20,7 @@
 
 from __future__ import absolute_import
 from __future__ import unicode_literals
-from dnfpluginscore import _, logger
+from dnfpluginscore import _
 
 import dnf
 import dnf.exceptions
diff --git a/plugins/repoquery.py b/plugins/repoquery.py
index c4ebccc..ddce24c 100644
--- a/plugins/repoquery.py
+++ b/plugins/repoquery.py
@@ -24,6 +24,7 @@ from dnfpluginscore import _
 import dnf
 import dnf.cli
 import dnf.exceptions
+import dnf.subject
 import dnfpluginscore
 import functools
 import hawkey
@@ -62,6 +63,8 @@ def build_format_fn(opts):
         return info_format
     elif opts.queryfilelist:
         return filelist_format
+    elif opts.querysourcerpm:
+        return sourcerpm_format
     else:
         return rpm2py_format(opts.queryformat).format
 
@@ -72,6 +75,9 @@ def info_format(pkg):
 def filelist_format(pkg):
     return "\n".join(pkg.files)
 
+def sourcerpm_format(pkg):
+    return pkg.sourcerpm
+
 def parse_arguments(args):
     # Setup ArgumentParser to handle util
     parser = dnfpluginscore.ArgumentParser(RepoQueryCommand.aliases[0])
@@ -81,6 +87,8 @@ def parse_arguments(args):
                         help=_('show only results from this REPO'))
     parser.add_argument('--arch', metavar='ARCH',
                         help=_('show only results from this ARCH'))
+    parser.add_argument('-f', '--file', metavar='FILE',
+                        help=_('show only results that owns FILE'))
     parser.add_argument('--whatprovides', metavar='REQ',
                         help=_('show only results there provides REQ'))
     parser.add_argument('--whatrequires', metavar='REQ',
@@ -96,6 +104,9 @@ def parse_arguments(args):
     outform.add_argument('-l', "--list", dest='queryfilelist',
                          default=False, action='store_true',
                          help=_('show list of files in the package'))
+    outform.add_argument('-s', "--source", dest='querysourcerpm',
+                         default=False, action='store_true',
+                         help=_('show package source RPM name'))
     outform.add_argument('--qf', "--queryformat", dest='queryformat',
                          default=QFORMAT_DEFAULT,
                          help=_('format for displaying found packages'))
@@ -193,17 +204,19 @@ class RepoQueryCommand(dnf.cli.Command):
             print(QUERY_TAGS)
             return
 
-        q = self.base.sack.query().available()
         if opts.key:
-            if set(opts.key) & set('*[?'):  # is pattern ?
-                fdict = {'name__glob': opts.key}
-            else:  # substring
-                fdict = {'name': opts.key}
-            q = q.filter(hawkey.ICASE, **fdict)
+            q = dnf.subject.Subject(opts.key, ignore_case=True).get_best_query(
+                self.base.sack, with_provides=False)
+        else:
+            q = self.base.sack.query()
+        # do not show packages from @System repo
+        q = q.available()
         if opts.repoid:
             q = q.filter(reponame=opts.repoid)
         if opts.arch:
             q = q.filter(arch=opts.arch)
+        if opts.file:
+            q = q.filter(file=opts.file)
         if opts.whatprovides:
             q = self.by_provides(self.base.sack, [opts.whatprovides], q)
         if opts.whatrequires:
diff --git a/rel-eng/README b/rel-eng/README
new file mode 100644
index 0000000..c103bdf
--- /dev/null
+++ b/rel-eng/README
@@ -0,0 +1,7 @@
+Release instructions:
+
+1. Tag: tito tag
+2. Test: tito build --rpm --offline
+3. Push: git push && git push $ORIGIN $TAG
+4. Release: tito release fedora-git
+
diff --git a/rel-eng/packages/.readme b/rel-eng/packages/.readme
new file mode 100644
index 0000000..8999c8d
--- /dev/null
+++ b/rel-eng/packages/.readme
@@ -0,0 +1,3 @@
+the rel-eng/packages directory contains metadata files
+named after their packages. Each file has the latest tagged
+version and the project's relative directory.
diff --git a/rel-eng/packages/dnf-plugins-core b/rel-eng/packages/dnf-plugins-core
new file mode 100644
index 0000000..d7b9e19
--- /dev/null
+++ b/rel-eng/packages/dnf-plugins-core
@@ -0,0 +1 @@
+0.1.5-2 ./
diff --git a/rel-eng/releasers.conf b/rel-eng/releasers.conf
new file mode 100644
index 0000000..ffeb709
--- /dev/null
+++ b/rel-eng/releasers.conf
@@ -0,0 +1,3 @@
+[fedora-git]
+releaser = tito.release.FedoraGitReleaser
+branches = f21
diff --git a/rel-eng/tito.props b/rel-eng/tito.props
new file mode 100644
index 0000000..1084386
--- /dev/null
+++ b/rel-eng/tito.props
@@ -0,0 +1,5 @@
+[buildconfig]
+builder = tito.distributionbuilder.DistributionBuilder
+tagger = tito.tagger.ReleaseTagger
+changelog_do_not_remove_cherrypick = 1
+changelog_format = %s (%a)
diff --git a/scripts/lint b/scripts/lint
index 8b4f630..5423164 100755
--- a/scripts/lint
+++ b/scripts/lint
@@ -21,8 +21,9 @@ disable "-d W0141" # used builtin 'map' function
 disable "-d W0142" # used star magic
 
 VAR_NAMES=--variable-rgx='[a-z_][a-z0-9_]*$'
+DUMMY_NAMES='--dummy-variables-rgx=_.*'
 MISC=--max-line-length=82
 
-pylint --rcfile=/dev/null --reports=n $DISABLED "$VAR_NAMES" $MISC $* \
+pylint --rcfile=/dev/null --reports=n $DISABLED "$VAR_NAMES" "$DUMMY_NAMES" $MISC $* \
     "$TEMPLATE" \
     | egrep -v -f $FALSE_POSITIVES
diff --git a/tests/README b/tests/README
new file mode 100644
index 0000000..5672629
--- /dev/null
+++ b/tests/README
@@ -0,0 +1,8 @@
+To run tests from the source tree, run this
+(working directory = root of the source tree):
+
+export PYTHONPATH=./plugins
+nosetests tests/
+
+You can run tests under specific Python version using nosetests-2.7
+or nosetests-3.4.
diff --git a/tests/test_config_manager.py b/tests/test_config_manager.py
new file mode 100644
index 0000000..64c581d
--- /dev/null
+++ b/tests/test_config_manager.py
@@ -0,0 +1,121 @@
+# Copyright (C) 2015  Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+# Public License for more details.  You should have received a copy of the
+# GNU General Public License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from tests.support import mock, RepoStub
+
+import config_manager
+import dnf
+import filecmp
+import iniparse
+import os
+import shutil
+import tempfile
+import unittest
+
+
+REPOLABEL = "testrepo"
+REPOCONTENT = """[testrepo]
+name=TestRepo
+baseurl=file:///tmp
+enabled=1
+"""
+
+class ConfigManagerBase(mock.MagicMock):
+      conf = dnf.conf.Conf()
+
+class ConfigManagerCommandTest(unittest.TestCase):
+
+    def setUp(self):
+        cli = dnf.cli.cli.Cli(ConfigManagerBase())
+        self.cmd = config_manager.ConfigManagerCommand(cli)
+        self.cmd.base.conf.reposdir = [tempfile.mkdtemp()]
+
+        self.addCleanup(shutil.rmtree, self.cmd.base.conf.reposdir[0])
+
+    def test_add_from_repofile(self):
+        tempfile_kwargs = {'mode': 'w', 'suffix': '.repo', 'delete': False}
+        if dnf.pycomp.PY3:
+            tempfile_kwargs['encoding'] = 'utf8'
+
+        repofile = tempfile.NamedTemporaryFile(**tempfile_kwargs)
+        dnf.pycomp.write_to_file(repofile, REPOCONTENT)
+        repofile.close()
+
+        args = ['--add-repo', repofile.name]
+        self.cmd.configure(args)
+        self.cmd.run(args)
+
+        installed_repofile = os.path.join(self.cmd.base.conf.reposdir[0],
+                                          os.path.basename(repofile.name))
+        with open(installed_repofile) as f:
+            added = f.read()
+
+        self.assertMultiLineEqual(REPOCONTENT, added)
+
+        def get_matching(x):
+            repo = dnf.repo.Repo(x, '/tmp')
+            repo.repofile = installed_repofile
+            repo.cfg = iniparse.compat.RawConfigParser()
+            repo.cfg.read(installed_repofile)
+            return [repo]
+        self.cmd.base.repos.get_matching = get_matching
+        self.cmd.base.output = dnf.cli.output.Output(self.cmd.base, self.cmd.base.conf)
+
+        self.subtest_disable(REPOLABEL, installed_repofile)
+        self.subtest_enable(REPOLABEL, installed_repofile)
+
+        os.unlink(repofile.name)
+
+    def test_add_from_repourl(self):
+        long_url = 'file:///tmp/%s' %  ('/'.join([str(x) for x in range(1,100)]))
+        name = 'tmp_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21' \
+               + '_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39' \
+               + '_40_41_42_43_44_45_46_47_48_49_50_51_52_53_54_55_56_57' \
+               + '_58_59_60_61_62_63__b3085fd1c13941e151ce4afe9d8670774d' \
+               + 'c8e19395f2a785bc1d210eccc0869b'
+        repofile = name + '.repo'
+
+        repocontent = "[%s]\nname=created by dnf config-manager from %s\n" \
+                      "baseurl=%s\nenabled=1\n" % (name, long_url, long_url)
+        args = ['--add-repo', long_url]
+        self.cmd.configure(args)
+        self.cmd.run(args)
+        with open(os.path.join(self.cmd.base.conf.reposdir[0], repofile)) as f:
+            added = f.read()
+
+        self.assertMultiLineEqual(repocontent, added)
+
+    def subtest_disable(self, label, fname):
+        args = ['--set-disabled', label]
+        self.cmd.configure(args)
+        self.cmd.run(args)
+
+        with open(fname) as f:
+            added = f.read()
+        disabled = REPOCONTENT.replace("enabled=1", "enabled=0")
+        self.assertMultiLineEqual(disabled, added)
+
+    def subtest_enable(self, label, fname):
+        args = ['--set-enabled', label]
+        self.cmd.configure(args)
+        self.cmd.run(args)
+
+        with open(fname) as f:
+            added = f.read()
+        self.assertMultiLineEqual(REPOCONTENT, added)
diff --git a/tests/test_download.py b/tests/test_download.py
index 4158171..7bee1b6 100644
--- a/tests/test_download.py
+++ b/tests/test_download.py
@@ -170,8 +170,8 @@ class DownloadlCommandTest(unittest.TestCase):
         self.assertFalse(repos['foobar-source'].enabled)
         self.cmd._enable_source_repos()
         self.assertTrue(repos['foo-source'].enabled)
-        self.assertFalse(repos['foo'].enabled)
-        self.assertFalse(repos['bar'].enabled)
+        self.assertTrue(repos['foo'].enabled)
+        self.assertTrue(repos['bar'].enabled)
         self.assertFalse(repos['foobar-source'].enabled)
 
     def test_get_source_packages(self):
diff --git a/tests/test_repoquery.py b/tests/test_repoquery.py
index 771a583..bf32857 100644
--- a/tests/test_repoquery.py
+++ b/tests/test_repoquery.py
@@ -46,6 +46,8 @@ EXPECTED_FILELIST_FORMAT = """\
 /var/foobar\
 """
 
+EXPECTED_SOURCERPM_FORMAT = """\
+foo-1.0.1-1.f20.src.rpm"""
 
 class PkgStub(object):
     def __init__(self):
@@ -81,6 +83,9 @@ class ArgParseTest(unittest.TestCase):
         opts, _ = repoquery.parse_arguments(['--provides'])
         self.assertEqual(opts.queryformat, '%{provides}')
 
+    def test_file(self):
+        opts, _ = repoquery.parse_arguments(['/var/foobar'])
+        self.assertIsNone(opts.file)
 
 class InfoFormatTest(unittest.TestCase):
     def test_info(self):
@@ -94,6 +99,11 @@ class FilelistFormatTest(unittest.TestCase):
         self.assertEqual(repoquery.filelist_format(pkg),
                          EXPECTED_FILELIST_FORMAT)
 
+class SourceRPMFormatTest(unittest.TestCase):
+    def test_info(self):
+        pkg = repoquery.PackageWrapper(PkgStub())
+        self.assertEqual(repoquery.sourcerpm_format(pkg),
+                         EXPECTED_SOURCERPM_FORMAT)
 
 class OutputTest(unittest.TestCase):
     def test_output(self):