summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--COPYING339
-rw-r--r--COPYING.lib504
-rw-r--r--ChangeLog1790
-rw-r--r--Makefile187
-rw-r--r--README14
-rw-r--r--bin/Makefile98
-rwxr-xr-xbin/createrepo2
-rwxr-xr-xbin/mergerepo2
-rwxr-xr-xbin/modifyrepo2
-rw-r--r--createrepo.bash115
-rw-r--r--createrepo.spec80
-rw-r--r--createrepo/Makefile64
-rw-r--r--createrepo/__init__.py1344
-rw-r--r--createrepo/deltarpms.py124
-rw-r--r--createrepo/merge.py139
-rw-r--r--createrepo/readMetadata.py217
-rw-r--r--createrepo/utils.py143
-rw-r--r--createrepo/yumbased.py235
-rwxr-xr-xdmd.py156
-rw-r--r--docs/Makefile99
-rw-r--r--docs/createrepo.8139
-rw-r--r--docs/mergerepo.156
-rw-r--r--docs/modifyrepo.141
-rwxr-xr-xgenpkgmetadata.py277
-rwxr-xr-xmergerepo.py85
-rwxr-xr-xmodifyrepo.py148
-rwxr-xr-xworker.py99
27 files changed, 6499 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..e77696a
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/COPYING.lib b/COPYING.lib
new file mode 100644
index 0000000..8add30a
--- /dev/null
+++ b/COPYING.lib
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..fb537ec
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,1790 @@
+2011-01-26 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, createrepo/__init__.py: mark as 0.9.9
+
+2011-01-26 Seth Vidal <skvidal@fedoraproject.org>
+
+ Merge branch 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo
+ * 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo:
+ Override timestamp check on repos. for mergerepo (like repodiff).
+ Add createrepo --workers (non)completion. Add modifyrepo option
+ completion.
+
+2011-01-21 James Antill <james@and.org>
+
+ * createrepo/merge.py: Override timestamp check on repos. for
+ mergerepo (like repodiff).
+
+2011-01-03 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: make sure when we want to look for rpms we
+ say .rpm not rpm b/c with the latter we catch .drpm files, too. :(
+
+2010-11-02 Ville Skyttä <ville.skytta@iki.fi>
+
+ * createrepo.bash: Add createrepo --workers (non)completion.
+
+2010-11-02 Ville Skyttä <ville.skytta@iki.fi>
+
+ * createrepo.bash: Add modifyrepo option completion.
+
+2010-10-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, createrepo/__init__.py: - add yum 3.2.29 requirement b/c of the small change I needed to
+ repoMDObject.py - set it to use /usr/share/createrepo/worker.py
+
+2010-10-07 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: remove libxml2 import from __init__.py :)
+
+
+2010-10-07 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: make createrepo use the repomd/repodata
+ mechanism from yum for making a repomd.xml which simplifies the code
+ dramatically since we don't have to mess with xml in here.
+
+2010-10-07 Seth Vidal <skvidal@fedoraproject.org>
+
+ * modifyrepo.py: fix up the usage output for modifyrepo
+
+2010-09-10 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, worker.py: - make sure we handle remote_url pkgs correctly until we get the
+ worker hooked up to handle them - if there are no pkgs to handle,
+ don't launch workers with nothing to do. - give better output from
+ the workers and have them obey -v/-q - everyone loves callbacks!
+
+2010-09-09 Seth Vidal <skvidal@fedoraproject.org>
+
+ * Makefile, createrepo/__init__.py, createrepo/utils.py,
+ createrepo/yumbased.py, genpkgmetadata.py, worker.py: create a
+ worker script for createrepo so createrepo can fork off N processes
+ to handle the md gathering from pkgs. This should speed up results
+ on systems which have been cpubound on the createrepo process. If
+ you're io bound it won't help you at all, and MAY make it worse.
+ many misc issues to iron out here - not the least of which is the
+ callback output and gathering stdout/stderr from the workers
+
+2010-08-20 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: handle broken locking on nfs target dirs
+ better if database is true. - sqlite dbs don't like being made on
+ locations without locking available. - if we know we're going to be
+ creating dbs then we should attempt to lock before doing anything
+ else and bail out nicely if we can't get an exclusive lock
+
+2010-08-19 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec: require yum 3.2.28 due to the imports in
+ modifyrepo
+
+2010-08-19 Seth Vidal <skvidal@fedoraproject.org>
+
+ * docs/modifyrepo.1: document --mdtype option
+
+2010-08-19 Seth Vidal <skvidal@fedoraproject.org>
+
+ * modifyrepo.py: - add option parsing for --mdtype - use the yum repomd objects to
+ read/write the repomd.xml - add mdtype option to RepoMetadata.add()
+ method
+
+2010-06-11 Seth Vidal <skvidal@fedoraproject.org>
+
+ Merge branch 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo
+ * 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo:
+ Don't use /usr/bin/env ... it's evil --database is now the default
+ for mergerepo too, have --no-database in completions instead.
+
+2010-06-11 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: - add option to createrepo config to collapse libc.so.6 requires -
+ this will only work with yum 3.2.28 and beyond
+
+2010-06-03 James Antill <james@and.org>
+
+ * modifyrepo.py: Don't use /usr/bin/env ... it's evil
+
+2010-06-02 Ville Skyttä <ville.skytta@iki.fi>
+
+ * createrepo.bash: --database is now the default for mergerepo too,
+ have --no-database in completions instead.
+
+2010-06-01 Seth Vidal <skvidal@fedoraproject.org>
+
+ * mergerepo.py: whoops - no-database shouldn't default to true!
+
+2010-06-01 Seth Vidal <skvidal@fedoraproject.org>
+
+ * mergerepo.py: add --no-database to mergrepo, too
+
+2010-05-31 Ville Skyttä <ville.skytta@iki.fi>
+
+ * createrepo.bash: --database is now the default, have --no-database
+ in completions instead.
+
+2010-05-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * docs/createrepo.8: update the docs for --no-database
+
+2010-05-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: make -d/--database the
+ default add --no-database in case someone somewhere needs to do
+ that
+
+2010-04-26 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: fixme comments about try/excepting the
+ database creation calls due to a weird issue with locks not working
+ on a nfs mount and createreepo tracing back with a TypeError of all
+ things
+
+2010-04-21 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/readMetadata.py: if we cannot
+ find one of the old repodata files make the warning look more dire
+ make sure we look for the 'repodata' subdir inside update_md_path
+
+2010-04-21 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: when the update_md_path doesn't exist -
+ emit a warning of some kind - rather than a somewhat quieter message
+ from MetadataIndex() this is mostly to help jesse b/c he asked
+ nicely
+
+2010-04-16 Colin Walters <walters@fedoraproject.org>
+
+ * genpkgmetadata.py: if we're not a tty, don't use the progress
+ output
+
+2010-04-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ Merge branch 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo
+ * 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo:
+ Tell git to ignore tarballs. Document --repo in man page. Add
+ bash completion.
+
+2010-04-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: - catch errors when moving the olddir out/back - if we get a
+ yumLocalPackage object in our pkglist we should record we read it.
+
+2010-03-05 Ville Skyttä <ville.skytta@iki.fi>
+
+ * .gitignore: Tell git to ignore tarballs.
+
+2010-03-05 Ville Skyttä <ville.skytta@iki.fi>
+
+ * docs/createrepo.8: Document --repo in man page.
+
+2010-02-19 Ville Skyttä <ville.skytta@iki.fi>
+
+ * Makefile, createrepo.bash, createrepo.spec: Add bash completion.
+
+2010-03-05 Seth Vidal <skvidal@fedoraproject.org>
+
+ Merge branch 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo
+ * 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo:
+ Trim trailing whitespace.
+
+2010-03-05 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: add repo tags and
+ --repo option to describe the repo itself. request from suse.
+
+2010-02-12 Ville Skyttä <ville.skytta@iki.fi>
+
+ * createrepo/__init__.py, createrepo/deltarpms.py,
+ createrepo/merge.py, createrepo/readMetadata.py,
+ createrepo/utils.py, createrepo/yumbased.py, dmd.py,
+ genpkgmetadata.py, mergerepo.py, modifyrepo.py: Trim trailing
+ whitespace.
+
+2010-02-11 Seth Vidal <skvidal@fedoraproject.org>
+
+ * genpkgmetadata.py: add option for setting max-delta-rpm-size for
+ pkgs which are too large to be delta'd.
+
+2010-02-10 Seth Vidal <skvidal@fedoraproject.org>
+
+ Merge branch 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo
+ * 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo:
+ Make *Emacs unsuspicious about trailing whitespace. Fix --exclude
+ -> --excludes typo. Add missing spaces in various help strings.
+
+2010-02-10 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: add --read-pkgs-list
+ option to output list of pkgs actually read. completely optional
+ and only really useful with --update or a --cachedir for what pkgs
+ DID get read/parsed.
+
+2010-02-09 Ville Skyttä <ville.skytta@iki.fi>
+
+ * Makefile: Make *Emacs unsuspicious about trailing whitespace.
+
+2010-02-09 Ville Skyttä <ville.skytta@iki.fi>
+
+ * docs/createrepo.8: Fix --exclude -> --excludes typo.
+
+2010-02-09 Ville Skyttä <ville.skytta@iki.fi>
+
+ * genpkgmetadata.py: Add missing spaces in various help strings.
+
+2010-02-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ Merge branch 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo
+ * 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo:
+ Add missing space in --checkts help string. Ignore *.py[co].
+ Remove outdated comment about --baseurl.
+
+2010-02-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, docs/createrepo.8, genpkgmetadata.py: - make --unique-md-filenames the default - add simple-md-filenames
+ to be able to disable the above if desired
+
+2010-01-18 Ville Skyttä <ville.skytta@iki.fi>
+
+ * genpkgmetadata.py: Add missing space in --checkts help string.
+
+2009-09-25 Ville Skyttä <ville.skytta@iki.fi>
+
+ * .gitignore: Ignore *.py[co].
+
+2009-01-26 Ville Skyttä <ville.skytta@iki.fi>
+
+ * docs/createrepo.8: Remove outdated comment about --baseurl. At
+ least yum uses it nowadays.
+
+2010-01-07 Dennis Gregorovic <dgregor@redhat.com>
+
+ * createrepo/readMetadata.py: Fixed, convert stat mtime to int so
+ comparison can work, --update, BZ 553030
+
+2010-01-07 Dennis Gregorovic <dgregor@redhat.com>
+
+ * createrepo/readMetadata.py: Convert stat mtime to int so
+ comparison can work, --update, BZ 553030
+
+2010-01-06 Dennis Gregorovic <dgregor@redhat.com>
+
+ * createrepo/__init__.py: Change baseurl of "old" packages on
+ update, when baseurl specified
+
+2009-10-05 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: apply fix for
+ https://bugzilla.redhat.com/show_bug.cgi?id=527259 make sure we're
+ not only testing the first pkg. Test all of them until we find one
+ that is newer.
+
+2009-09-14 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec: add requirement on python-deltarpm for new patch
+ from Bill.
+
+2009-09-14 Bill Nottingham <notting@redhat.com>
+
+ * createrepo/deltarpms.py: createrepo patch to use the new deltarpm
+ python bindings
+
+2009-08-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * ChangeLog: changelog merge
+
+2009-08-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, createrepo/__init__.py: mark as 0.9.8
+
+2009-08-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * docs/createrepo.8: add man page entry for -n/--includepkg
+
+2009-08-25 Seth Vidal <skvidal@fedoraproject.org>
+
+ * genpkgmetadata.py: add -n, --includepkg option to allow users to
+ specify urls to pkgs to include in the repo on the cli - like a
+ fully cli version of --pkglist/-i
+
+2009-08-25 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: capture ioerror/oserrors on handling
+ metadata files and emit a proper MDError fixes rh bug:
+ https://bugzilla.redhat.com/show_bug.cgi?id=514995
+
+2009-08-18 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: commit obviously broken pragma setting fix
+ from mikeb@redhat.com
+
+2009-07-21 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: fix for
+ https://bugzilla.redhat.com/show_bug.cgi?id=512610 take timestamp of
+ repomd.xml - not of repodata dir - just in case repodata dir is
+ empty, for some bizarre reason
+
+2009-06-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: remove extra 0 from max_delta_rpm_size
+
+2009-06-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: more/better output about makedeltarpm
+ timing
+
+2009-06-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: output how long it took to make the drpm
+ file
+
+2009-06-16 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: - prestodelta.xml file generation is now roughly 60X faster than it
+ was before - python unicode string concatenation sucks, a lot. -
+ add a delta xml generation profile output - get rid of some
+ incorrect output about db files and delta metadata - get rid of some
+ old not-useful comments in the code
+
+2009-05-14 James Antill <james@and.org>
+
+ * genpkgmetadata.py: Make the UI for --checksum a bit nicer
+
+2009-05-14 James Antill <james@and.org>
+
+ * docs/createrepo.8: Fix -profile in man page, to be --profile
+
+2009-05-14 James Antill <james@and.org>
+
+ * docs/createrepo.8: Add some more documentation about --checksum
+
+2009-05-13 James Antill <james@and.org>
+
+ * createrepo/__init__.py: Add open-size and size fo *_db MD. Fix
+ file to stat for *.xml.gz size
+
+2009-05-13 James Antill <james@and.org>
+
+ Merge branch 'master' of
+ ssh://yum.baseurl.org/srv/projects/createrepo/git/createrepo *
+ 'master' of
+ ssh://yum.baseurl.org/srv/projects/createrepo/git/createrepo: if
+ our deltarpm dir doesn't exist, don't go looking for it - and
+ definitely
+
+2009-05-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: if our deltarpm dir doesn't exist, don't
+ go looking for it - and definitely don't traceback
+
+2009-05-05 James Antill <james@and.org>
+
+ Merge branch 'size-in-repomd.xml' * size-in-repomd.xml: Add
+ size to the repomd.xml output
+
+2009-04-24 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: pylint fixes for __init__ - lots of line
+ cleanups and a couple of potential bugs.
+
+2009-04-24 Seth Vidal <skvidal@fedoraproject.org>
+
+ * modifyrepo.py: pylint clean up on modifyrepo
+
+2009-04-24 Seth Vidal <skvidal@fedoraproject.org>
+
+ * genpkgmetadata.py: genpkgmetadata.py pylint cleanup.
+
+2009-04-22 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: if we've not enabled the deltas, don't try
+ to do them. silly, but harmless in this case
+
+2009-04-21 Tim Lauridsen <timlau@fedoraproject.org>
+
+ * createrepo/__init__.py: pylint: fixed Uses of a deprecated module
+ 'string'
+
+2009-04-21 Tim Lauridsen <timlau@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/utils.py: pylint: fixed
+ Redefining built-in
+
+2009-04-21 Tim Lauridsen <timlau@fedoraproject.org>
+
+ * createrepo/deltarpms.py, createrepo/yumbased.py: pylint: fixed
+ unused imports
+
+2009-04-21 Tim Lauridsen <timlau@fedoraproject.org>
+
+ * createrepo/readMetadata.py: pylint: fixed Bad indentation
+
+2009-04-21 Tim Lauridsen <timlau@fedoraproject.org>
+
+ * Makefile: Added the pylint basic and disabled the warning we dont
+ care about
+
+2009-04-18 James Antill <james@and.org>
+
+ * createrepo/__init__.py: Fix copy and paste error on message
+
+2009-04-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py: make sure our
+ sumtype specified propagates down to the pkg checksums, too
+
+2009-04-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: set a max size option so we don't kill
+ systems with < memory than deltarpm likes to use.
+
+2009-04-17 James Antill <james@and.org>
+
+ * createrepo/yumbased.py: Use the same checksum type for the key, as
+ for the data in the key
+
+2009-04-16 Seth Vidal <skvidal@fedoraproject.org>
+
+ * genpkgmetadata.py: remove the deprecation notice since: 1. it
+ works fine 2. there is a legit use for it
+
+2009-04-16 Seth Vidal <skvidal@fedoraproject.org>
+
+ * docs/createrepo.8: document the deltarpm options
+
+2009-04-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: it helps to have the right order of items
+ in the pkgtup :(
+
+2009-04-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: quiet down output
+
+2009-04-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: make sure we don't try to sqlite the
+ prestodelta xml, yet.
+
+2009-04-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: add missing '>'
+
+2009-04-13 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/utils.py, modifyrepo.py: make
+ sure the checksum type we use is being used everywhere. closes
+ rhbug: https://bugzilla.redhat.com/show_bug.cgi?id=494951
+
+2009-03-24 Seth Vidal <skvidal@fedoraproject.org>
+
+ * ChangeLog: changelog merge
+
+2009-03-24 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, createrepo/__init__.py: 0.9.7 require yum 3.2.22
+
+
+2009-02-09 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/deltarpms.py: when we process
+ the rpms only do the drpm creation. after we're done take the drpms
+ and generate the metadata from there
+
+2009-02-03 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/merge.py: some fixes and to make it work on
+ rhel5/python2.4
+
+2009-02-03 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: and one more mistake
+
+2009-02-03 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: correct tabbing so createrepo works when
+ you're NOT using deltas
+
+2009-01-29 Seth Vidal <skvidal@fedoraproject.org>
+
+ * genpkgmetadata.py: add --num-deltas option
+
+2009-01-29 Seth Vidal <skvidal@fedoraproject.org>
+
+ Merge branch 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo
+ * 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo:
+ Add missing documentation on --checksum and --profile
+
+2009-01-29 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/deltarpms.py,
+ genpkgmetadata.py: --deltas, enable the creation and
+ metadata-creation for presto/deltarpms
+
+2009-01-27 James Antill <james@and.org>
+
+ Merge branch 'master' of
+ ssh://yum.baseurl.org/srv/projects/createrepo/git/createrepo *
+ 'master' of
+ ssh://yum.baseurl.org/srv/projects/createrepo/git/createrepo: make
+ modifyrepo behave with sha256 as the default checksum
+
+2009-01-27 James Antill <james@and.org>
+
+ * docs/createrepo.8: Add missing documentation on --checksum and
+ --profile
+
+2009-01-27 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/utils.py, modifyrepo.py: make modifyrepo behave with
+ sha256 as the default checksum
+
+2009-01-26 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py, genpkgmetadata.py:
+ make sha256 the default checksum type everywhere
+
+2009-01-23 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/merge.py: add init options to specify your own yumbase
+ object, mdconf object md generator class
+
+2009-01-22 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/utils.py: make sure we keep working on python 2.4 :(
+
+2009-01-22 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/utils.py: use gzip.name not gzip.filename to avoid
+ python 2.6 deprecation warnings
+
+2009-01-22 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: get rid of the md5 badness - use yum's
+ Checksum class so we don't have to deal with python 2.4 vs 2.6isms
+
+2009-01-22 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: add --profile option to
+ the cli interface so profile info is outputted only when it is used.
+
+
+2009-01-19 James Antill <james@and.org>
+
+ * createrepo/yumbased.py: Use correct cachedir after rename
+
+2008-12-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: allow alternative path
+ for --update via --update-md-path, So your old repodata does not
+ have to be in the path you want to look through.
+
+2008-10-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * modifyrepo.py: try/excepts on modifyrepo so we don't smack the
+ user with a traceback
+
+2008-10-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * ChangeLog: remerge changelog
+
+2008-10-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * bin/Makefile: minor changes to the make file so that it will make
+ a proper archive :)
+
+2008-10-27 Seth Vidal <skvidal@fedoraproject.org>
+
+ * ChangeLog: merge changelog
+
+2008-10-23 Seth Vidal <skvidal@fedoraproject.org>
+
+ * modifyrepo.py: allow already-compressed metadata files to work and
+ not be double-compressed
+
+2008-10-21 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, docs/Makefile, docs/mergerepo.1, mergerepo.py:
+ mergerepo man page todos added to mergerepo
+
+2008-10-21 Seth Vidal <skvidal@fedoraproject.org>
+
+ Merge branch 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo
+ * 'master' of
+ ssh://createrepo.baseurl.org/srv/projects/createrepo/git/createrepo:
+ Change the NamedTemporaryFile() usage to mkstemp(), stupid API Fix
+ parallel updates to the cachedir, thx to Michael Schwendt for
+ spotting it
+
+2008-10-21 Seth Vidal <skvidal@fedoraproject.org>
+
+ * AUTHORS, README, createrepo.spec, createrepo/__init__.py,
+ docs/createrepo.8, genpkgmetadata.py: - document --content, --distro and --revision - update urls in spec
+ and docs - add Authors file
+
+2008-10-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * Makefile: add merge repo here, too
+
+2008-10-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * mergerepo.py: pylintian cleanups
+
+2008-10-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, createrepo/__init__.py: bump version to 0.9.6 -
+ change the dep to yum 3.2.20 - since it is what WILL be needed
+
+2008-10-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * bin/Makefile, bin/mergerepo, createrepo/merge.py, mergerepo.py:
+ add mergerepo
+
+2008-10-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: add arbitrary metadata to config options
+ for api callers
+
+2008-10-09 James Antill <james@and.org>
+
+ * createrepo/yumbased.py: Change the NamedTemporaryFile() usage to
+ mkstemp(), stupid API
+
+2008-10-08 James Antill <james@and.org>
+
+ * createrepo/yumbased.py: Fix parallel updates to the cachedir, thx
+ to Michael Schwendt for spotting it
+
+2008-09-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec: bump the yum requirement to 3.2.19..
+
+2008-09-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/utils.py: remove unused utf8String function from utils
+ - move most of it into yum.misc
+
+2008-09-12 Seth Vidal <skvidal@fedoraproject.org>
+
+ * genpkgmetadata.py: make the profile option work again
+
+2008-08-13 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py, genpkgmetadata.py:
+ remove most of the yumbased code, disable database-only for now
+
+2008-08-08 James Antill <james@and.org>
+
+ * createrepo/__init__.py: Add size to the repomd.xml output
+
+2008-08-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/utils.py,
+ createrepo/yumbased.py: minor changes for handling a packagesack
+ and/or list of package objects as the pkglist to create the repo
+ from also rename the xml_dump functions - eventually going to remove
+ them.
+
+2008-06-17 James Antill <james@and.org>
+
+ * docs/createrepo.8: Add missing doc. for --skip-stat option
+
+2008-06-05 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py, genpkgmetadata.py:
+ some fixmes and starts - and recommit a working --database-only
+
+2008-05-12 James Antill <james@and.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: Remove -n option, it's
+ a noop atm. anyway
+
+2008-05-12 James Antill <james@and.org>
+
+ * createrepo/__init__.py: Pass just dir. to getFileList(), makes -C
+ work. Fixes bug#446040
+
+2008-04-16 James Antill <james@and.org>
+
+ * createrepo/utils.py: Talk to libxml maintainer ... tweak
+
+2008-04-16 James Antill <james@and.org>
+
+ * createrepo/utils.py: Just remove bad small bytes, like 0x01 atm.
+
+2008-04-02 James Antill <james@and.org>
+
+ * docs/createrepo.8: Add some missing options to man page
+
+2008-03-11 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/utils.py: a few tweaks to speed
+ up the database creation
+
+2008-03-11 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/utils.py,
+ createrepo/yumbased.py, docs/createrepo.8: more or less complete
+ createrepo --database-only
+
+2008-03-11 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py, genpkgmetadata.py: - partial patch to enable --database-only output from createrepo -
+ still to implement filelists direct to sqlite, changelogs direct to
+ sqlite - this check in is just a hedge against loss from my laptop,
+ do not use the feature in this commit
+
+2008-03-03 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: better name for node
+
+2008-03-03 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py: exclude rpmlib
+ requires from metadata b/c they are silly store them in the
+ repomd.xml per-repo so we have them if we ever actually need them
+
+2008-02-29 James Antill <james@and.org>
+
+ * genpkgmetadata.py: Fix line overflow, minor IO optimisation
+
+2008-02-20 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py, genpkgmetadata.py:
+ allow --pkglist or self.conf.pkglist to be a list of arbitrary urls
+ to packages. createrepo will download the remote urls to a tempdir,
+ read them in and add them to the metadata.
+
+2008-02-18 Seth Vidal <skvidal@fedoraproject.org>
+
+ * ChangeLog: merge changelog
+
+2008-02-18 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, createrepo/__init__.py: bump version numbers
+
+2008-02-18 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py: - clean up some garbage spaces and an extra 'return' - write some
+ notes on something interesting to do for completely arbitrary
+ repositories - make sure that under no circumstances will a package
+ that we cannot get a pkgid/checksum from will be in the metadata.
+ And it will output an error message
+
+2008-02-13 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: raise, don't print
+
+2008-02-12 Luke Macken <lmacken@redhat.com>
+
+ * genpkgmetadata.py: Clean up some more unused modules
+
+2008-02-12 Luke Macken <lmacken@redhat.com>
+
+ * createrepo/__init__.py, createrepo/utils.py: If we want to use
+ MDError in utils.py, we need to define it outside of __init__ to
+ avoid circular deps
+
+2008-02-12 Luke Macken <lmacken@redhat.com>
+
+ * createrepo/__init__.py: Pull in createrepo.utils.errorprint in our
+ __init__ module.
+
+2008-02-12 Luke Macken <lmacken@redhat.com>
+
+ * createrepo/__init__.py: Import shutil since we use it in
+ createrepo.__init__
+
+2008-02-12 Luke Macken <lmacken@redhat.com>
+
+ * createrepo/__init__.py: s/conf.checkts/self.conf.checkts/
+
+2008-02-12 Luke Macken <lmacken@redhat.com>
+
+ * createrepo/__init__.py, createrepo/readMetadata.py,
+ createrepo/yumbased.py: Remove a bunch of module imports that we
+ aren't using. One of which being 'hashlib', which prevents
+ createrepo from running on anything by Python 2.5.
+
+2008-01-31 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/readMetadata.py,
+ createrepo/utils.py, modifyrepo.py: - make sure group files are compressed/sha-named - add group_gz
+ section for compressed group file - add addArbitraryMetadata()
+ method to MetaDataGenerator class - fix up modifyrepo to generate
+ sha-named files - make modifyrepo act a bit more like createrepo for
+ its operations
+
+2008-01-30 Seth Vidal <skvidal@fedoraproject.org>
+
+ * ChangeLog: changelog merge
+
+2008-01-29 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: make sure things work
+ out as the right default
+
+2008-01-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, createrepo/__init__.py: bump ver to 0.9.4 in spec
+ and module
+
+2008-01-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: make sure non-unique-md-filenanmes-repos
+ cleanup sqlite files if we switch to unique-md-filenames
+
+2008-01-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: clean up old versions of primary,
+ filelists and other that are lingering in the repodata dir due to
+ sha1-addition
+
+2008-01-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: swap around the filename creation order so
+ it doesn't make leaves files around
+
+2008-01-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/readMetadata.py: make
+ readMetadata.py take its metadata file locations from repomd.xml
+ using the yum parser for it. complete --unique-md-filenames
+ implementation
+
+2008-01-28 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: part of
+ --unique-md-filenames is complete. This lets createrepo generate
+ metadata files with the file's checksum included in the filename.
+ This helps it work more nicely with proxies. --update support will
+ not work with --unique-md-filenames at the moment. Need to read in
+ repomd.xml to make that work.
+
+2008-01-22 Seth Vidal <skvidal@fedoraproject.org>
+
+ * ChangeLog: changelog merge
+
+2008-01-22 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, createrepo/__init__.py: 0.9.3
+
+2008-01-22 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py, genpkgmetadata.py:
+ add changelog-limit option to restrict the number of changelogs we
+ add, by default
+
+2008-01-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * genpkgmetadata.py: make sure empty directories still work
+
+2008-01-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * ChangeLog: update changelog
+
+2008-01-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: comment out a debug print
+
+2008-01-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py: re-enable
+ --cachedir in code fix logic issues in cachedir from 0.4.X
+
+2008-01-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: - fix more problems with relative paths and --split - revert
+ cachedir disabling - cachedir isn't working yet, but it's no longer
+ deprecated due to a use case I hadn't considered.
+
+2008-01-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: make --update and --split mostly work
+ again
+
+2008-01-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: - move a bunch of tests into the base class - deprecate --cachedir -
+ make it enable --update instead b/c it is MUCH faster
+
+2008-01-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, createrepo/__init__.py: bump version to 0.9.2
+
+2008-01-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: add some more correct outputs about the
+ sqlite db generation
+
+2008-01-17 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: --split works again,
+ fix relative paths with ../../ curse in the direction of --split and
+ --basedir :(
+
+2008-01-16 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/utils.py,
+ createrepo/yumbased.py, genpkgmetadata.py: clean up api to simplify
+ all of the code calling it probably severely break --split for the
+ moment, though
+
+2008-01-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: make sure we have empty stubs for items in
+ the rpm-format section, older versions of yum will go bonkers if
+ not.
+
+2008-01-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: - add debug output for database time generation
+
+2008-01-15 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/readMetadata.py,
+ genpkgmetadata.py: - add --skip-stat to skip the stat() call on updates - this is
+ mostly b/c the fedora createrepo call is done over nfs and that is
+ VERY VERY slow for 20K stat() calls :( - fix --pkglist - patch
+ from jesse keating - document options in --help output
+
+2008-01-14 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/yumbased.py: - add copyright statements and licenses to top of files - start
+ function to remove all the directory mauling in genpkgmetadata -
+ fixmes
+
+2008-01-11 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: return src for arch when it's a srpm
+
+2008-01-10 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: make sure that files are run through the
+ xml escaping, too.
+
+2008-01-09 Seth Vidal <skvidal@fedoraproject.org>
+
+ * ChangeLog, Makefile: update changelog, add changelog target to
+ makefile
+
+2008-01-09 Seth Vidal <skvidal@fedoraproject.org>
+
+ * genpkgmetadata.py: clean up old comments and cruft
+
+2008-01-09 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/readMetadata.py,
+ genpkgmetadata.py: - clean up interface a lot - add callback interface for progress
+ output - more proper catching of exceptions - remove improper
+ sys.exit() calls from the base class
+
+2008-01-09 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: make --update work correctly - by getting
+ the right item from os.stat() for mtime
+
+2008-01-09 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: free up memory in the changelog output
+ used by generating the xml node
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: clean up a debug output
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/readMetadata.py,
+ createrepo/yumbased.py: fix up a lot of xml creation errors and make
+ --update work again
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, createrepo/__init__.py: bump to 0.9.1
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: and a little more utf8'ing - just for
+ completeness and insanity
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: utf8 files, too :(
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: all of the xml tools suck, this is
+ evidence of them fall back to the generating the xml via libxml2 for
+ the changelog hopefully this won't make memory explode
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: politely bounce over damaged rpms - output
+ an error report new errorlog() method - can be easily replaced in
+ subclass
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: remove debug prints :)
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py: make sure that we check for nonexistent
+ items in the hdr
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: make it a more proper ts
+
+2008-01-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py: try except on package opening
+
+2008-01-07 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/yumbased.py, genpkgmetadata.py: - clean out old classes from yumbased.py - clean out debug prints
+ from genpkgmetadata.py
+
+2008-01-07 Seth Vidal <skvidal@fedoraproject.org>
+
+ * genpkgmetadata.py: make the version stuff make sense
+
+2008-01-07 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo.spec, genpkgmetadata.py: - make rpmbuild work - mark a fixme
+
+2008-01-03 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, createrepo/utils.py,
+ createrepo/yumbased.py, genpkgmetadata.py: - port to optionparser from getopt - redo config class to make use
+ outside of cli more do-able - handle repomd.xml creation in class,
+ too - still have a lot of changes to complete
+
+2007-12-20 Seth Vidal <skvidal@fedoraproject.org>
+
+ * createrepo/__init__.py, genpkgmetadata.py: a little more
+ class-full
+
+2007-12-20 Seth Vidal <skvidal@fedoraproject.org>
+
+ * Makefile, bin/Makefile, createrepo.spec, createrepo/Makefile,
+ createrepo/__init__.py, createrepo/readMetadata.py,
+ createrepo/utils.py, createrepo/yumbased.py, docs/Makefile,
+ dumpMetadata.py, genpkgmetadata.py, readMetadata.py: Whew: this is
+ the beginning of a big conversion of createrepo to use the yum
+ modules, behave more like a modular program and have a proper class
+ structure. It's not done, but it's a start.
+
+2007-12-18 Seth Vidal <skvidal@fedoraproject.org>
+
+ Merge branch 'master' of
+ ssh://login.linux.duke.edu/home/groups/createrepo/git/createrepo *
+ 'master' of
+ ssh://login.linux.duke.edu/home/groups/createrepo/git/createrepo:
+ Update ChangeLog Remove some unnecessary imports Better unicode
+ handling in modifyrepo Add a man page for modifyrepo
+
+2007-12-18 Seth Vidal <skvidal@fedoraproject.org>
+
+ * docs/createrepo.8, dumpMetadata.py, genpkgmetadata.py,
+ modifyrepo.py: merge maintenance changes up to head before we nuke
+ it from orbit
+
+2007-12-06 Luke Macken <lmacken@redhat.com>
+
+ * ChangeLog: Update ChangeLog
+
+2007-12-06 Luke Macken <lmacken@redhat.com>
+
+ * dumpMetadata.py, readMetadata.py: Remove some unnecessary imports
+
+
+2007-12-06 Luke Macken <lmacken@redhat.com>
+
+ * modifyrepo.py: Better unicode handling in modifyrepo
+
+2007-12-03 Luke Macken <lmacken@redhat.com>
+
+ * ChangeLog, createrepo.spec, docs/Makefile, docs/modifyrepo.1,
+ modifyrepo.py: Add a man page for modifyrepo
+
+2007-11-14 Seth Vidal <skvidal@fedoraproject.org>
+
+ * genpkgmetadata.py: merge pkglist option to HEAD
+
+2007-08-08 Seth Vidal <skvidal@fedoraproject.org>
+
+ * README: update readme, point to better url, clean up explanation
+
+2007-07-01 James Bowes <jbowes@redhat.com>
+
+ * dmd.py: Add delta metadata diff and patch script
+
+2007-06-07 Paul Nasrat <pnasrat@redhat.com>
+
+ * createrepo.spec: Bump version
+
+2007-06-07 Paul Nasrat <pnasrat@redhat.com>
+
+ * ChangeLog, Makefile: Prepare for release
+
+2007-06-07 Paul Nasrat <pnasrat@redhat.com>
+
+ * Makefile, docs/createrepo.8, genpkgmetadata.py, readMetadata.py:
+ This patch adds a --update option to createrepo.
+
+ https://lists.dulug.duke.edu/pipermail/rpm-metadata/2007-March/000756.html Patch from Mike Bonnet <mikeb@redhat.com>
+
+2007-05-18 Paul Nasrat <pnasrat@redhat.com>
+
+ * dumpMetadata.py: Fix for older rpm versions Christoph Thiel
+ <cthiel@suse.de>
+
+2007-05-16 Paul Nasrat <pnasrat@redhat.com>
+
+ * ChangeLog, Makefile, createrepo.spec, genpkgmetadata.py: Update
+ ChangeLog Bump version to 0.4.9
+
+2007-05-16 Paul Nasrat <pnasrat@redhat.com>
+
+ * dumpMetadata.py: Figure out appropriate dbversion Jeremy Katz
+ <katzj@redhat.com>
+
+2007-05-16 Paul Nasrat <pnasrat@redhat.com>
+
+ * dumpMetadata.py: createrepo-0.4.8-cachefix.patch * changes the way the hashkey
+ for the cache is generated. (The original version just used
+ rpm.RPMTAG_SIGMD5, which was the same for the same signed and
+ unsigned package. However, this lead to a wrong checksum
+ ending up in the metadata.) Christoph Thiel <cthiel@suse.de>
+
+2007-05-16 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: createrepo-0.4.8-skip-symlinks.patch * adds an option to skip
+ symlinks (-S, --skip-symlinks) Christoph Thiel <cthiel@suse.de>
+
+2007-02-13 Seth Vidal <skvidal@linux.duke.edu>
+
+ * ChangeLog: update changelog, again
+
+2007-02-13 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile: add copying and copying.lib to makefile for 'make
+ archive'
+
+2007-02-13 Seth Vidal <skvidal@linux.duke.edu>
+
+ * ChangeLog: check in changelog
+
+2007-02-13 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile: and makefile ver
+
+2007-02-13 Seth Vidal <skvidal@linux.duke.edu>
+
+ * createrepo.spec, genpkgmetadata.py: mark as 0.4.8
+
+2007-02-08 Paul Nasrat <pnasrat@redhat.com>
+
+ * COPYING.lib, createrepo.spec: Add LGPL file
+
+2007-02-08 Paul Nasrat <pnasrat@redhat.com>
+
+ * COPYING, createrepo.spec: Add COPYING
+
+2007-02-07 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: merge in Christoph Thiel's patch to fix string
+ conversion for odd EVR's
+
+2007-02-07 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: merge Jesse Keatings' patch to find groups file
+ properly
+
+2007-02-07 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile: ver number in Makefile
+
+2007-02-06 Seth Vidal <skvidal@linux.duke.edu>
+
+ * createrepo.spec: yum-metadata-parser dep and new version number
+
+2007-02-06 Seth Vidal <skvidal@linux.duke.edu>
+
+ * docs/createrepo.8: update docs for -d
+
+2007-02-06 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: 0.4.7 version number
+
+2007-02-04 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: make database version listed in repomd
+
+2007-02-04 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: add dbversion to sqlite metadata in repomd.
+
+2007-02-03 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: default to max compression
+
+2007-02-03 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: make the sqlite file names not look stupid
+
+2007-02-03 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: allow optionally creating
+ compressed sqlite databases
+
+2006-10-22 Luke Macken <lmacken@redhat.com>
+
+ * ChangeLog, modifyrepo.py: use the mdname for the 'href' element,
+ so it doesn't explode when dealing with xml.dom.minidom.Document
+ objects.
+
+2006-10-14 Luke Macken <lmacken@redhat.com>
+
+ * ChangeLog, Makefile, bin/Makefile, createrepo.spec: 2006-10-14
+ 01:30 lmacken * Makefile, bin/Makefile, createrepo.spec:
+ Makefile changes for modifyrepo, and added it to the spec as
+ well.
+
+2006-08-23 Luke Macken <lmacken@redhat.com>
+
+ * ChangeLog, bin/modifyrepo, modifyrepo.py: 2006-08-23 15:40
+ lmacken * modifyrepo.py, bin/modifyrepo: Initial import
+
+2006-08-11 Paul Nasrat <pnasrat@redhat.com>
+
+ * ChangeLog: Update changelog with cvs2cl
+
+2006-08-11 Paul Nasrat <pnasrat@redhat.com>
+
+ * createrepo.spec: update date
+
+2006-08-11 Paul Nasrat <pnasrat@redhat.com>
+
+ * docs/createrepo.8, genpkgmetadata.py: Patch from Hans-Peter Jansen
+ <hpj@urpla.net> -C, --checkts option added to avoid metadata
+ generation, if ctime filestamps are up to date. It's currently
+ mutually exclusive with the --split option.
+
+2006-07-28 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Fix cache output dir to 0.4.5 behaviour
+
+2006-07-28 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Fix filtering out path from file list and
+ passing correct path to writeMetaData
+
+2006-07-28 Paul Nasrat <pnasrat@redhat.com>
+
+ * test/testMetaDataGenerator.py, test/testSplitMetaDataGenerator.py:
+ nuke tests for now
+
+2006-07-21 Paul Nasrat <pnasrat@redhat.com>
+
+ * Makefile: Bump
+
+2006-07-20 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Make splitmetadata handler do it' own
+ getFileList to correctly manipulate paths.
+
+2006-07-20 Paul Nasrat <pnasrat@redhat.com>
+
+ * test/testSplitMetaDataGenerator.py: Improve tests for split cases
+
+
+2006-07-20 Paul Nasrat <pnasrat@redhat.com>
+
+ * test/testSplitMetaDataGenerator.py: duplicate for split tests
+
+2006-07-20 Paul Nasrat <pnasrat@redhat.com>
+
+ * test/testMetaDataGenerator.py: More consistent naming Relative and
+ parallel dir testing
+
+2006-07-20 Paul Nasrat <pnasrat@redhat.com>
+
+ * test/testMetaDataGenerator.py: Refactor tests, add additional
+ tests
+
+2006-07-20 Paul Nasrat <pnasrat@redhat.com>
+
+ * test/testMetaDataGenerator.py: Start unit testing so we don't
+ regress behaviour
+
+2006-07-20 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Set outputdir correctly
+
+2006-07-20 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Move to split basedir and directory everywhere
+ to preserve command line paths. Use os.path.walk rather than our own
+ implementation Improve error messages
+
+2006-07-19 Paul Nasrat <pnasrat@redhat.com>
+
+ * createrepo.spec: genpkgmetadata.py
+
+2006-07-19 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Consistent directory handling and errors
+
+2006-07-19 Paul Nasrat <pnasrat@redhat.com>
+
+ * dumpMetadata.py: Patch from hpj@urpla.net to use a more robust rpm
+ header signature retrieval method for cache files, as recommended by
+ Jeff Johnson.
+
+2006-07-19 Luke Macken <lmacken@redhat.com>
+
+ * ChangeLog, createrepo.spec: 2006-07-19 14:23 lmacken *
+ createrepo.spec: remove python-urlgrabber dependency
+
+2006-07-19 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Tolerate unknown files in repodata dirs - Ville
+ Skyttä
+
+2006-07-19 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: fix up relative paths (#199228)
+
+2006-06-30 Paul Nasrat <pnasrat@redhat.com>
+
+ * dumpMetadata.py: Fix srpm detection for rpm-4.4.6 and later
+
+2006-06-26 Seth Vidal <skvidal@linux.duke.edu>
+
+ * ChangeLog: overwrite changelog
+
+2006-06-15 Luke Macken <lmacken@redhat.com>
+
+ * ChangeLog, docs/createrepo.8, genpkgmetadata.py: 2006-06-15 11:40
+ lmacken * genpkgmetadata.py, docs/createrepo.8: Revert
+ --update-info-location patch, since yum now supports arbitrary
+ metadata via YumRepository::retrieveMD()
+
+2006-06-09 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: include Panu's patch to
+ support --noepoch for use with old versions of rpm
+
+2006-06-09 Seth Vidal <skvidal@linux.duke.edu>
+
+ * createrepo.spec: fix the dep
+
+2006-06-09 Seth Vidal <skvidal@linux.duke.edu>
+
+ * createrepo.spec, genpkgmetadata.py: fix versions and bump by one.
+ Thanks to Gareth Armstrong for noticing this.
+
+2006-03-04 Paul Nasrat <pnasrat@redhat.com>
+
+ * ChangeLog: add changelog
+
+2006-03-04 Paul Nasrat <pnasrat@redhat.com>
+
+ * createrepo.spec: release
+
+2006-02-21 Paul Nasrat <pnasrat@redhat.com>
+
+ * Makefile, createrepo.spec, docs/createrepo.8, genpkgmetadata.py:
+ Documentation and version updates
+
+2006-02-21 Paul Nasrat <pnasrat@redhat.com>
+
+ * dumpMetadata.py, genpkgmetadata.py: Enable seperate outputdir
+ (dgregor)
+
+2006-02-18 Luke Macken <lmacken@redhat.com>
+
+ * docs/createrepo.8, genpkgmetadata.py: Add support for -U
+ (--update-info-location) flag to query a specified server for
+ package update metadata. The metadata will currently be stored in
+ 'repodata/update-info' and each package in the primary.xml will have
+ an <update id="FEDORA-XXXX-XX"
+ location="update-info/pkg-ver-rel.xml"/> tag which points to it's
+ corresponding update information.
+
+2006-01-13 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: apply two patches from
+ dgregor@redhat.com - verifies that the checksum cache file is more
+ recent than the corresponding rpm - move around cmds dict
+ initialization to make it more consistent.
+
+2005-12-08 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Fix cachedir/groupfile handling with --basedir
+ and using paths not relative to cwd when run without --basedir.
+
+2005-12-08 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Support --split option to label media with urls
+ across directories.
+
+2005-12-08 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Split out processing into smaller methods.
+ Make ts internal. Files and base/file/other data attributes.
+
+2005-12-08 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Cleanup of generator class to use cmds
+ internally as an attribute.
+
+2005-12-08 Paul Nasrat <pnasrat@redhat.com>
+
+ * genpkgmetadata.py: Initial work to form metadata generator class.
+
+
+2005-11-27 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: speed up by caching file mode lookup by Dennis
+ Gregorovic
+
+2005-11-11 Paul Nasrat <pnasrat@redhat.com>
+
+ * dumpMetadata.py, genpkgmetadata.py: Enable basedir to be used -
+ dgregor
+
+2005-11-02 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: patch from Christoph Thiel to
+ make it work on suse 9.3 and to allow for non absolute-path cache
+ dirs.
+
+2005-08-11 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: turn off all signature checking when reading in
+ headers
+
+2005-07-24 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: document that the -g option is for a file
+ relative to the directory you are creating the repository for.
+
+2005-07-14 Seth Vidal <skvidal@linux.duke.edu>
+
+ * docs/createrepo.8: man page for cachedir
+
+2005-07-14 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec, genpkgmetadata.py: 0.4.3
+
+2005-07-11 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: - disable the checksum flag - default and only use sha1sum's - add
+ in -c,--cachedir option to setup a cachedir for the cache files of
+ the checksums of the packages. Uses name-hdrid from the package hdr
+ as filenames. Contents of the file is a single line of the package's
+ checksum. This dramatically speeds up rebuilding a repository's
+ metadata b/c the checksum of the package file was the item taking
+ the most time.
+
+2005-05-29 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: Apply Arun Bhanu's patch to
+ add in --quiet and --verbose options instead of just -q and -v
+
+2005-03-30 Seth Vidal <skvidal@linux.duke.edu>
+
+ * docs/Makefile: fix mandir path for docs
+
+2005-01-18 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, docs/Makefile: fix the Makefiles, f'real
+
+2005-01-18 Seth Vidal <skvidal@linux.duke.edu>
+
+ * docs/Makefile, docs/createrepo.8: real commit
+
+2005-01-18 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec: adding man page and upating the
+ Makefiles and specfile accordingly. Thanks Bob Kashani for the man
+ page.
+
+2005-01-18 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: need to seek to the beginning before doing a new
+ read operation.
+
+2005-01-17 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec: spec and Makefile to 0.4.2
+
+2005-01-17 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: move around gzipOpen for use
+ in another program relabel 0.4.2
+
+2005-01-07 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: single open for all file
+ operations. about a 30% time savings.
+
+2004-11-02 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: small fix for --exclude to work. -x works, but
+ --exclude didn't, now it is fixed
+
+2004-10-21 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec, genpkgmetadata.py: update version
+ numbers
+
+2004-10-21 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: problem with ghost entries not showing up in
+ primary.xml even if they matched the regex strings.
+
+2004-10-18 Seth Vidal <skvidal@linux.duke.edu>
+
+ * bin/createrepo: whoops! need to quote that var string
+
+2004-10-11 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec, genpkgmetadata.py: correct problem with
+ handling dirs with a space in the filename update version number
+
+2004-10-04 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: clean up argument parsing to handle --version
+ and --help more correctly. Not quite the patch Ville Skyttä
+ submitted.
+
+2004-09-30 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: one more place to tag
+
+2004-09-30 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec: update to 0.3.9
+
+2004-09-30 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: checksum of group file will be wrong if specified
+ - didn't seek(0) after copying it.
+
+2004-09-20 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: made 'cannot remove old metadata dir' a
+ non-fatal error. It just warns.
+
+2004-09-20 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: updated to default to sha-1 checksums
+
+2004-09-11 Seth Vidal <skvidal@linux.duke.edu>
+
+ * createrepo.spec, genpkgmetadata.py: update spec file as 0.3.8 fix
+ for bug in command handling of groups location
+
+2004-09-11 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile: fix for group file path being wrong - Bill Nottingham
+ mark as 0.3.8
+
+2004-09-11 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: fix for error when string is
+ None for utf8 conversion
+
+2004-09-03 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile: Makefile update to fix a bug reported by Anvil
+
+2004-09-01 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec, genpkgmetadata.py: 0.3.7
+
+2004-08-27 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: patch from Ville Skytta (this
+ a will be wrong, sorry) to correct decoding/encoding problems.
+
+2004-07-23 Seth Vidal <skvidal@linux.duke.edu>
+
+ * README: updated readme with anoncvs location
+
+2004-07-23 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec, genpkgmetadata.py: ver to 0.3.6
+
+2004-07-23 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: fix filelists to be complete
+
+2004-07-23 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: remove a debug print call
+
+2004-07-23 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec: mark as 0.3.5
+
+2004-07-23 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: fix up for broken filelists in packages
+
+2004-07-23 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: silly string fix
+
+2004-07-20 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec, genpkgmetadata.py: bump number to 0.3.4
+
+
+2004-07-20 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: re-enabled group files
+ documented it
+
+2004-06-30 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: add pre=1 to requires entries for prereq marking
+
+
+2004-06-30 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: - include xmlns:rpm in main metadata tag rather than per-format node
+ - fix output for sorta-list, sorta-string rpm header tags
+
+2004-06-28 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: fix for namespace for license, vendor, group,
+ buildhost and sourcerpm was None, should have been formatns (rpm
+ namespace)
+
+2004-06-09 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec, genpkgmetadata.py: mark as 0.3.3
+
+2004-06-06 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: included a not-that-terribly accurate package
+ count
+
+2004-06-05 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: generate uncompressed
+ checksums a much easier way.
+
+2004-06-05 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: revert some changes
+
+2004-06-03 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: fix stupid version thing
+
+2004-06-03 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: keep checksum of uncompressed
+ metadata files in repomd.xml
+
+2004-06-03 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: move versioned prco from
+ separate string to properties of the entry
+
+2004-04-16 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile: fix makefile
+
+2004-04-16 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec, genpkgmetadata.py: update to 0.3.2
+ added -p or --pretty flag to make it pretty-print the xml output not
+ pretty printing the output makes the import a lot faster and the
+ data smaller
+
+2004-01-18 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec, dumpMetadata.py, genpkgmetadata.py: 1. make it actually work :) 2. bump to 0.3.1
+
+2004-01-18 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, createrepo.spec: add README for real *boggle*
+
+2004-01-18 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, README, createrepo.spec: tagged Makefile and createrepo
+ as 0.3 Add README to both of the above
+
+2004-01-18 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: make metadata files be written to repodata/
+
+2004-01-17 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: fix bug where not all files
+ were getting included make the directory detection more reliable
+
+2004-01-14 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, bin/Makefile, createrepo.spec, dumpMetadata.py,
+ genpkgmetadata.py: fixed memory use problem updated spec for 0.2
+ fixed makefile dumbness fixed problems with broken symlinks
+
+2004-01-13 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: catch some errors on broken symlinks
+
+2004-01-11 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Makefile, bin/Makefile, bin/createrepo, createrepo.spec,
+ dumpMetadata.py, genpkgmetadata.py: - translation stubs - makefiles - spec file - bin wrapper
+
+2004-01-10 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: silly updates in comments
+
+2004-01-10 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: CVS Id Tags
+
+2004-01-10 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py: [no log message]
+
+2004-01-10 Seth Vidal <skvidal@linux.duke.edu>
+
+ * genpkgmetadata.py: added --version and __version__ string
+
+2004-01-10 Seth Vidal <skvidal@linux.duke.edu>
+
+ * dumpMetadata.py, genpkgmetadata.py: move two functions around to
+ more logically arrange the repomd.xml generating function
+
+2004-01-09 Seth Vidal <skvidal@linux.duke.edu>
+
+ * Initial revision
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..60bb9db
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,187 @@
+PKGNAME = createrepo
+VERSION=$(shell awk '/Version:/ { print $$2 }' ${PKGNAME}.spec)
+RELEASE=$(shell awk '/Release:/ { print $$2 }' ${PKGNAME}.spec)
+CVSTAG=createrepo-$(subst .,_,$(VERSION)-$(RELEASE))
+PYTHON=python
+SUBDIRS = $(PKGNAME) bin docs
+PYFILES = $(wildcard *.py)
+
+
+SHELL = /bin/sh
+top_srcdir = .
+srcdir = .
+prefix = /usr
+exec_prefix = ${prefix}
+
+bindir = ${exec_prefix}/bin
+sbindir = ${exec_prefix}/sbin
+libexecdir = ${exec_prefix}/libexec
+datadir = ${prefix}/share
+sysconfdir = ${prefix}/etc
+sharedstatedir = ${prefix}/com
+localstatedir = ${prefix}/var
+libdir = ${exec_prefix}/lib
+infodir = ${prefix}/info
+docdir =
+includedir = ${prefix}/include
+oldincludedir = /usr/include
+mandir = ${prefix}/share/man
+
+pkgdatadir = $(datadir)/$(PKGNAME)
+pkglibdir = $(libdir)/$(PKGNAME)
+pkgincludedir = $(includedir)/$(PKGNAME)
+top_builddir =
+
+# all dirs
+DIRS = $(DESTDIR)$(bindir) $(DESTDIR)$(sysconfdir)/bash_completion.d \
+ $(DESTDIR)$(pkgdatadir) $(DESTDIR)$(mandir)
+
+
+# INSTALL scripts
+INSTALL = install -p --verbose
+INSTALL_BIN = $(INSTALL) -m 755
+INSTALL_DIR = $(INSTALL) -m 755 -d
+INSTALL_DATA = $(INSTALL) -m 644
+INSTALL_MODULES = $(INSTALL) -m 755 -D
+RM = rm -f
+
+MODULES = $(srcdir)/genpkgmetadata.py \
+ $(srcdir)/modifyrepo.py \
+ $(srcdir)/mergerepo.py \
+ $(srcdir)/worker.py
+
+.SUFFIXES: .py .pyc
+.py.pyc:
+ python -c "import py_compile; py_compile.compile($*.py)"
+
+
+all: $(MODULES)
+ for subdir in $(SUBDIRS) ; do \
+ $(MAKE) -C $$subdir VERSION=$(VERSION) PKGNAME=$(PKGNAME) DESTDIR=$(DESTDIR); \
+ done
+
+check:
+ pychecker $(MODULES) || exit 0
+
+install: all installdirs
+ $(INSTALL_MODULES) $(srcdir)/$(MODULES) $(DESTDIR)$(pkgdatadir)
+ $(INSTALL_DATA) $(PKGNAME).bash $(DESTDIR)$(sysconfdir)/bash_completion.d
+ for subdir in $(SUBDIRS) ; do \
+ $(MAKE) -C $$subdir install VERSION=$(VERSION) PKGNAME=$(PKGNAME); \
+ done
+
+installdirs:
+ for dir in $(DIRS) ; do \
+ $(INSTALL_DIR) $$dir ; \
+ done
+
+
+uninstall:
+ for module in $(MODULES) ; do \
+ $(RM) $(pkgdatadir)/$$module ; \
+ done
+ for subdir in $(SUBDIRS) ; do \
+ $(MAKE) -C $$subdir uninstall VERSION=$(VERSION) PKGNAME=$(PKGNAME); \
+ done
+
+clean:
+ $(RM) *.pyc *.pyo
+ for subdir in $(SUBDIRS) ; do \
+ $(MAKE) -C $$subdir clean VERSION=$(VERSION) PKGNAME=$(PKGNAME); \
+ done
+
+distclean: clean
+ $(RM) -r .libs
+ $(RM) core
+ $(RM) *~
+ for subdir in $(SUBDIRS) ; do \
+ $(MAKE) -C $$subdir distclean VERSION=$(VERSION) PKGNAME=$(PKGNAME); \
+ done
+
+pylint:
+ @pylint --rcfile=test/createrepo-pylintrc *.py createrepo
+
+pylint-short:
+ @pylint -r n --rcfile=test/createrepo-pylintrc *.py createrepo
+
+mostlyclean:
+ $(MAKE) clean
+
+
+maintainer-clean:
+ $(MAKE) distclean
+ $(RM) $(srcdir)/configure
+
+changelog:
+ git log --pretty --numstat --summary | git2cl > ChangeLog
+
+dist:
+ olddir=`pwd`; \
+ distdir=$(PKGNAME)-$(VERSION); \
+ $(RM) -r .disttmp; \
+ $(INSTALL_DIR) .disttmp; \
+ $(INSTALL_DIR) .disttmp/$$distdir; \
+ $(MAKE) distfiles
+ distdir=$(PKGNAME)-$(VERSION); \
+ cd .disttmp; \
+ tar -cvz > ../$$distdir.tar.gz $$distdir; \
+ cd $$olddir
+ $(RM) -r .disttmp
+
+daily:
+ olddir=`pwd`; \
+ distdir=$(PKGNAME); \
+ $(RM) -r .disttmp; \
+ $(INSTALL_DIR) .disttmp; \
+ $(INSTALL_DIR) .disttmp/$$distdir; \
+ $(MAKE) dailyfiles
+ day=`/bin/date +%Y%m%d`; \
+ distdir=$(PKGNAME); \
+ tarname=$$distdir-$$day ;\
+ cd .disttmp; \
+ perl -pi -e "s/\#DATE\#/$$day/g" $$distdir/$(PKGNAME)-daily.spec; \
+ echo $$day; \
+ tar -cvz > ../$$tarname.tar.gz $$distdir; \
+ cd $$olddir
+ $(RM) -rf .disttmp
+
+dailyfiles:
+ distdir=$(PKGNAME); \
+ cp \
+ $(srcdir)/*.py \
+ $(srcdir)/Makefile \
+ $(srcdir)/ChangeLog \
+ $(srcdir)/COPYING \
+ $(srcdir)/COPYING.lib \
+ $(srcdir)/README \
+ $(srcdir)/$(PKGNAME).spec \
+ $(srcdir)/$(PKGNAME).bash \
+ $(top_srcdir)/.disttmp/$$distdir
+ for subdir in $(SUBDIRS) ; do \
+ $(MAKE) -C $$subdir dailyfiles VERSION=$(VERSION) PKGNAME=$(PKGNAME); \
+ done
+
+distfiles:
+ distdir=$(PKGNAME)-$(VERSION); \
+ cp \
+ $(srcdir)/*.py \
+ $(srcdir)/Makefile \
+ $(srcdir)/ChangeLog \
+ $(srcdir)/COPYING \
+ $(srcdir)/COPYING.lib \
+ $(srcdir)/README \
+ $(srcdir)/$(PKGNAME).spec \
+ $(srcdir)/$(PKGNAME).bash \
+ $(top_srcdir)/.disttmp/$$distdir
+ for subdir in $(SUBDIRS) ; do \
+ $(MAKE) -C $$subdir distfiles VERSION=$(VERSION) PKGNAME=$(PKGNAME); \
+ done
+
+archive: dist
+
+.PHONY: todo
+todo:
+ @echo ---------------===========================================
+ @grep -n TODO\\\|FIXME `find . -type f` | grep -v grep
+ @echo ---------------===========================================
+.PHONY: all install install-strip uninstall clean distclean mostlyclean maintainer-clean info dvi dist distfiles check installcheck installdirs daily dailyfiles
diff --git a/README b/README
new file mode 100644
index 0000000..99bd0dd
--- /dev/null
+++ b/README
@@ -0,0 +1,14 @@
+This program generates a repodata dir and xml files for a repository of
+rpm packages. This repository is compatible with apt/yum/smart/yast and many
+other package-repository-related tools.
+
+run createrepo -h for usage syntax
+
+http://createrepo.baseurl.org/
+
+
+
+
+
+
+
diff --git a/bin/Makefile b/bin/Makefile
new file mode 100644
index 0000000..8e0a7e2
--- /dev/null
+++ b/bin/Makefile
@@ -0,0 +1,98 @@
+SHELL = /bin/sh
+top_srcdir = ..
+srcdir = ../bin
+prefix = /usr
+exec_prefix = ${prefix}
+
+bindir = ${exec_prefix}/bin
+sbindir = ${exec_prefix}/sbin
+libexecdir = ${exec_prefix}/libexec
+datadir = ${prefix}/share
+sysconfdir = ${prefix}/etc
+sharedstatedir = ${prefix}/com
+localstatedir = ${prefix}/var
+libdir = ${exec_prefix}/lib
+infodir = ${prefix}/info
+docdir =
+includedir = ${prefix}/include
+oldincludedir = /usr/include
+mandir = ${prefix}/man
+
+pkgdatadir = $(datadir)/$(PKGNAME)
+pkglibdir = $(libdir)/$(PKGNAME)
+pkgincludedir = $(includedir)/$(PKGNAME)
+top_builddir = ../
+
+# all dirs
+DIRS = $(DESTDIR)$(bindir) $(DESTDIR)/etc $(DESTDIR)$(pkgdatadir)
+
+
+# INSTALL scripts
+INSTALL = install -p --verbose
+INSTALL_BIN = $(INSTALL) -m 755
+INSTALL_DIR = $(INSTALL) -m 755 -d
+INSTALL_DATA = $(INSTALL) -m 644
+INSTALL_MODULES = $(INSTALL) -m 755 -D
+RM = rm -f
+
+
+all:
+ echo "Nothing to do"
+
+
+install: all installdirs
+ $(INSTALL_BIN) $(srcdir)/$(PKGNAME) $(DESTDIR)$(bindir)/$(PKGNAME)
+ $(INSTALL_BIN) $(srcdir)/modifyrepo $(DESTDIR)$(bindir)/modifyrepo
+ $(INSTALL_BIN) $(srcdir)/mergerepo $(DESTDIR)$(bindir)/mergerepo
+
+
+uninstall:
+ $(RM) $(bindir)/$(PKGNAME)
+
+
+
+clean:
+
+
+distclean:
+ $(RM) -rf .libs
+ $(RM) -f core
+ $(RM) -f *~
+
+
+mostlyclean:
+ $(MAKE) clean
+
+
+maintainer-clean:
+ $(MAKE) distclean
+
+
+distfiles:
+ distdir=$(PKGNAME)-$(VERSION); \
+ mkdir $(top_srcdir)/.disttmp/$$distdir/bin;\
+ cp \
+ $(srcdir)/$(PKGNAME) \
+ $(srcdir)/Makefile \
+ $(srcdir)/modifyrepo \
+ $(srcdir)/mergerepo \
+ $(top_srcdir)/.disttmp/$$distdir/bin
+
+dailyfiles:
+ distdir=$(PKGNAME); \
+ mkdir $(top_srcdir)/.disttmp/$$distdir/bin;\
+ cp \
+ $(srcdir)/$(PKGNAME) \
+ $(srcdir)/Makefile \
+ $(srcdir)/modifyrepo \
+ $(srcdir)/mergerepo \
+ $(top_srcdir)/.disttmp/$$distdir/bin
+
+installdirs:
+ $(MAKE) -C $(top_srcdir) installdirs
+
+
+.PHONY: all install install-strip uninstall clean distclean mostlyclean maintainer-clean info dvi dist distfiles check installcheck installdirs dailyfiles
+
+
+
diff --git a/bin/createrepo b/bin/createrepo
new file mode 100755
index 0000000..b0de515
--- /dev/null
+++ b/bin/createrepo
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/share/createrepo/genpkgmetadata.py "$@"
diff --git a/bin/mergerepo b/bin/mergerepo
new file mode 100755
index 0000000..df6e2f1
--- /dev/null
+++ b/bin/mergerepo
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/share/createrepo/mergerepo.py "$@"
diff --git a/bin/modifyrepo b/bin/modifyrepo
new file mode 100755
index 0000000..c9732d8
--- /dev/null
+++ b/bin/modifyrepo
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/share/createrepo/modifyrepo.py "$@"
diff --git a/createrepo.bash b/createrepo.bash
new file mode 100644
index 0000000..54ac8b2
--- /dev/null
+++ b/createrepo.bash
@@ -0,0 +1,115 @@
+# bash completion for createrepo and friends
+
+_cr_createrepo()
+{
+ COMPREPLY=()
+
+ case $3 in
+ --version|-h|--help|-u|--baseurl|--distro|--content|--repo|--workers|\
+ --revision|-x|--excludes|--changelog-limit|--max-delta-rpm-size)
+ return 0
+ ;;
+ --basedir|-c|--cachedir|--update-md-path|-o|--outputdir|\
+ --oldpackagedirs)
+ COMPREPLY=( $( compgen -d -- "$2" ) )
+ return 0
+ ;;
+ -g|--groupfile)
+ COMPREPLY=( $( compgen -f -o plusdirs -X '!*.xml' -- "$2" ) )
+ return 0
+ ;;
+ -s|--sumtype)
+ COMPREPLY=( $( compgen -W 'md5 sha1 sha256 sha512' -- "$2" ) )
+ return 0
+ ;;
+ -i|--pkglist|--read-pkgs-list)
+ COMPREPLY=( $( compgen -f -o plusdirs -- "$2" ) )
+ return 0
+ ;;
+ -n|--includepkg)
+ COMPREPLY=( $( compgen -f -o plusdirs -X '!*.rpm' -- "$2" ) )
+ return 0
+ ;;
+ --num-deltas)
+ COMPREPLY=( $( compgen -W '1 2 3 4 5 6 7 8 9' -- "$2" ) )
+ return 0
+ ;;
+ esac
+
+ if [[ $2 == -* ]] ; then
+ COMPREPLY=( $( compgen -W '--version --help --quiet --verbose --profile
+ --excludes --basedir --baseurl --groupfile --checksum --pretty
+ --cachedir --checkts --no-database --update --update-md-path
+ --skip-stat --split --pkglist --includepkg --outputdir
+ --skip-symlinks --changelog-limit --unique-md-filenames
+ --simple-md-filenames --distro --content --repo --revision --deltas
+ --oldpackagedirs --num-deltas --read-pkgs-list
+ --max-delta-rpm-size --workers' -- "$2" ) )
+ else
+ COMPREPLY=( $( compgen -d -- "$2" ) )
+ fi
+} &&
+complete -F _cr_createrepo -o filenames createrepo genpkgmetadata.py
+
+_cr_mergerepo()
+{
+ COMPREPLY=()
+
+ case $3 in
+ --version|-h|--help|-a|--archlist)
+ return 0
+ ;;
+ -r|--repo|-o|--outputdir)
+ COMPREPLY=( $( compgen -d -- "$2" ) )
+ return 0
+ ;;
+ esac
+
+ COMPREPLY=( $( compgen -W '--version --help --repo --archlist --no-database
+ --outputdir --nogroups --noupdateinfo' -- "$2" ) )
+} &&
+complete -F _cr_mergerepo -o filenames mergerepo mergerepo.py
+
+_cr_modifyrepo()
+{
+ COMPREPLY=()
+
+ case $3 in
+ --version|-h|--help|--mdtype)
+ return 0
+ ;;
+ esac
+
+ if [[ $2 == -* ]] ; then
+ COMPREPLY=( $( compgen -W '--version --help --mdtype' -- "$2" ) )
+ return 0
+ fi
+
+ local i argnum=1
+ for (( i=1; i < ${#COMP_WORDS[@]}-1; i++ )) ; do
+ if [[ ${COMP_WORDS[i]} != -* &&
+ ${COMP_WORDS[i-1]} != @(=|--mdtype) ]]; then
+ argnum=$(( argnum+1 ))
+ fi
+ done
+
+ case $argnum in
+ 1)
+ COMPREPLY=( $( compgen -f -o plusdirs -- "$2" ) )
+ return 0
+ ;;
+ 2)
+ COMPREPLY=( $( compgen -d -- "$2" ) )
+ return 0
+ ;;
+ esac
+} &&
+complete -F _cr_modifyrepo -o filenames modifyrepo modifyrepo.py
+
+# Local variables:
+# mode: shell-script
+# sh-basic-offset: 4
+# sh-indent-comment: t
+# indent-tabs-mode: nil
+# End:
+# ex: ts=4 sw=4 et filetype=sh
diff --git a/createrepo.spec b/createrepo.spec
new file mode 100644
index 0000000..1e491cd
--- /dev/null
+++ b/createrepo.spec
@@ -0,0 +1,80 @@
+%{!?python_sitelib: %define python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
+
+Summary: Creates a common metadata repository
+Name: createrepo
+Version: 0.9.9
+Release: 1
+License: GPL
+Group: System Environment/Base
+Source: %{name}-%{version}.tar.gz
+URL: http://createrepo.baseurl.org/
+BuildRoot: %{_tmppath}/%{name}-%{version}root
+BuildArchitectures: noarch
+Requires: python >= 2.1, rpm-python, rpm >= 0:4.1.1, libxml2-python
+Requires: yum-metadata-parser, yum >= 3.2.29, python-deltarpm
+
+%description
+This utility will generate a common metadata repository from a directory of
+rpm packages
+
+%prep
+%setup -q
+
+%install
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+make DESTDIR=$RPM_BUILD_ROOT sysconfdir=%{_sysconfdir} install
+
+%clean
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+
+
+%files
+%defattr(-, root, root)
+%dir %{_datadir}/%{name}
+%doc ChangeLog README COPYING COPYING.lib
+%{_sysconfdir}/bash_completion.d/
+%{_datadir}/%{name}/*
+%{_bindir}/%{name}
+%{_bindir}/modifyrepo
+%{_bindir}/mergerepo
+%{_mandir}/man8/createrepo.8*
+%{_mandir}/man1/modifyrepo.1*
+%{_mandir}/man1/mergerepo.1*
+%{python_sitelib}/createrepo
+
+%changelog
+* Wed Jan 26 2011 Seth Vidal <skvidal at fedoraproject.org>
+- bump to 0.9.9
+- add worker.py
+
+* Thu Aug 19 2010 Seth Vidal <skvidal at fedoraproject.org>
+- increase yum requirement for the modifyrepo use of RepoMD, RepoData and RepoMDError
+
+* Fri Aug 28 2009 Seth Vidal <skvidal at fedoraproject.org>
+- 0.9.8
+
+* Tue Mar 24 2009 Seth Vidal <skvidal at fedoraproject.org>
+- 0.9.7
+
+* Fri Oct 17 2008 Seth Vidal <skvidal at fedoraproject.org>
+- add mergerepo - 0.9.6
+
+* Mon Feb 18 2008 Seth Vidal <skvidal at fedoraproject.org>
+- 0.9.5
+
+* Mon Jan 28 2008 Seth Vidal <skvidal at fedoraproject.org>
+- 0.9.4
+
+* Tue Jan 22 2008 Seth Vidal <skvidal at fedoraproject.org>
+- 0.9.3
+
+* Thu Jan 17 2008 Seth Vidal <skvidal at fedoraproject.org>
+- significant api changes
+
+* Tue Jan 8 2008 Seth Vidal <skvidal at fedoraproject.org>
+- 0.9.1 - lots of fixes
+- cleanup changelog, too
+
+* Thu Dec 20 2007 Seth Vidal <skvidal at fedoraproject.org>
+- beginning of the new version
+
diff --git a/createrepo/Makefile b/createrepo/Makefile
new file mode 100644
index 0000000..d3d3a34
--- /dev/null
+++ b/createrepo/Makefile
@@ -0,0 +1,64 @@
+PYTHON=python
+PACKAGE = $(shell basename `pwd`)
+PYFILES = $(wildcard *.py)
+PYVER := $(shell $(PYTHON) -c 'import sys; print "%.3s" %(sys.version)')
+PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print sys.prefix')
+PYLIBDIR = $(PYSYSDIR)/lib/python$(PYVER)
+PKGDIR = $(PYLIBDIR)/site-packages/$(PKGNAME)
+
+SHELL = /bin/sh
+top_srcdir = ..
+srcdir = ../$(PKGNAME)
+prefix = /usr
+exec_prefix = ${prefix}
+
+bindir = ${exec_prefix}/bin
+sbindir = ${exec_prefix}/sbin
+libexecdir = ${exec_prefix}/libexec
+datadir = ${prefix}/share
+sysconfdir = ${prefix}/etc
+sharedstatedir = ${prefix}/com
+localstatedir = ${prefix}/var
+libdir = ${exec_prefix}/lib
+infodir = ${prefix}/info
+docdir =
+includedir = ${prefix}/include
+oldincludedir = /usr/include
+mandir = ${datadir}/man
+
+pkgdatadir = $(datadir)/$(PKGNAME)
+pkglibdir = $(libdir)/$(PKGNAME)
+pkgincludedir = $(includedir)/$(PKGNAME)
+top_builddir = ../
+
+
+all:
+ echo "Nothing to do"
+
+clean:
+ rm -f *.pyc *.pyo *~
+
+install:
+ mkdir -p $(DESTDIR)/$(PKGDIR)
+ for p in $(PYFILES) ; do \
+ install -m 644 $$p $(DESTDIR)/$(PKGDIR)/$$p; \
+ done
+ $(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(PKGDIR)', 1, '$(PKGDIR)', 1)"
+
+distfiles:
+ distdir=$(PKGNAME)-$(VERSION); \
+ mkdir $(top_srcdir)/.disttmp/$$distdir/$(PKGNAME);\
+ cp \
+ $(srcdir)/$(PYFILES) \
+ $(srcdir)/Makefile \
+ $(top_srcdir)/.disttmp/$$distdir/$(PKGNAME)
+
+dailyfiles:
+ distdir=$(PKGNAME); \
+ mkdir $(top_srcdir)/.disttmp/$$distdir/$(PKGNAME);\
+ cp \
+ $(srcdir)/$(PYFILES) \
+ $(srcdir)/__init__.py \
+ $(srcdir)/Makefile \
+ $(top_srcdir)/.disttmp/$$distdir/$(PKGNAME)
+
diff --git a/createrepo/__init__.py b/createrepo/__init__.py
new file mode 100644
index 0000000..8f2538e
--- /dev/null
+++ b/createrepo/__init__.py
@@ -0,0 +1,1344 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Copyright 2009 Red Hat, Inc -
+# written by seth vidal skvidal at fedoraproject.org
+
+import os
+import sys
+import fnmatch
+import time
+import yumbased
+import shutil
+from bz2 import BZ2File
+from urlgrabber import grabber
+import tempfile
+import stat
+import fcntl
+import subprocess
+
+from yum import misc, Errors, to_unicode
+from yum.repoMDObject import RepoMD, RepoMDError, RepoData
+from yum.sqlutils import executeSQL
+from yum.packageSack import MetaSack
+from yum.packages import YumAvailablePackage, YumLocalPackage
+
+import rpmUtils.transaction
+from utils import _, errorprint, MDError
+import readMetadata
+try:
+ import sqlite3 as sqlite
+except ImportError:
+ import sqlite
+
+try:
+ import sqlitecachec
+except ImportError:
+ pass
+
+from utils import _gzipOpen, bzipFile, checkAndMakeDir, GzipFile, \
+ checksum_and_rename, split_list_into_equal_chunks
+import deltarpms
+
+__version__ = '0.9.9'
+
+
+class MetaDataConfig(object):
+ def __init__(self):
+ self.quiet = False
+ self.verbose = False
+ self.profile = False
+ self.excludes = []
+ self.baseurl = None
+ self.groupfile = None
+ self.sumtype = 'sha256'
+ self.pretty = False
+ self.cachedir = None
+ self.use_cache = False
+ self.basedir = os.getcwd()
+ self.checkts = False
+ self.split = False
+ self.update = False
+ self.deltas = False # do the deltarpm thing
+ # where to put the .drpms - defaults to 'drpms' inside 'repodata'
+ self.deltadir = None
+ self.delta_relative = 'drpms/'
+ self.oldpackage_paths = [] # where to look for the old packages -
+ self.deltafile = 'prestodelta.xml.gz'
+ self.num_deltas = 1 # number of older versions to delta (max)
+ self.max_delta_rpm_size = 100000000
+ self.update_md_path = None
+ self.skip_stat = False
+ self.database = True
+ self.outputdir = None
+ self.file_patterns = ['.*bin\/.*', '^\/etc\/.*', '^\/usr\/lib\/sendmail$']
+ self.dir_patterns = ['.*bin\/.*', '^\/etc\/.*']
+ self.skip_symlinks = False
+ self.pkglist = []
+ self.database_only = False
+ self.primaryfile = 'primary.xml.gz'
+ self.filelistsfile = 'filelists.xml.gz'
+ self.otherfile = 'other.xml.gz'
+ self.repomdfile = 'repomd.xml'
+ self.tempdir = '.repodata'
+ self.finaldir = 'repodata'
+ self.olddir = '.olddata'
+ self.mdtimestamp = 0
+ self.directory = None
+ self.directories = []
+ self.changelog_limit = None # needs to be an int or None
+ self.unique_md_filenames = True
+ self.additional_metadata = {} # dict of 'type':'filename'
+ self.revision = str(int(time.time()))
+ self.content_tags = [] # flat list of strings (like web 2.0 tags)
+ self.distro_tags = []# [(cpeid(None allowed), human-readable-string)]
+ self.repo_tags = []# strings, forwhatever they are worth
+ self.read_pkgs_list = None # filepath/name to write out list of pkgs
+ # read in this run of createrepo
+ self.collapse_glibc_requires = True
+ self.workers = 1 # number of workers to fork off to grab metadata from the pkgs
+ self.worker_cmd = '/usr/share/createrepo/worker.py'
+
+ #self.worker_cmd = './worker.py' # helpful when testing
+
+class SimpleMDCallBack(object):
+ def errorlog(self, thing):
+ print >> sys.stderr, thing
+
+ def log(self, thing):
+ print thing
+
+ def progress(self, item, current, total):
+ sys.stdout.write('\r' + ' ' * 80)
+ sys.stdout.write("\r%d/%d - %s" % (current, total, item))
+ sys.stdout.flush()
+
+
+class MetaDataGenerator:
+ def __init__(self, config_obj=None, callback=None):
+ self.conf = config_obj
+ if config_obj == None:
+ self.conf = MetaDataConfig()
+ if not callback:
+ self.callback = SimpleMDCallBack()
+ else:
+ self.callback = callback
+
+
+ self.ts = rpmUtils.transaction.initReadOnlyTransaction()
+ self.pkgcount = 0
+ self.current_pkg = 0
+ self.files = []
+ self.rpmlib_reqs = {}
+ self.read_pkgs = []
+
+ if not self.conf.directory and not self.conf.directories:
+ raise MDError, "No directory given on which to run."
+
+ if not self.conf.directories: # just makes things easier later
+ self.conf.directories = [self.conf.directory]
+ if not self.conf.directory: # ensure we have both in the config object
+ self.conf.directory = self.conf.directories[0]
+
+ # the cachedir thing:
+ if self.conf.cachedir:
+ self.conf.use_cache = True
+
+ # this does the dir setup we need done
+ self._parse_directory()
+ self._test_setup_dirs()
+
+ def _parse_directory(self):
+ """pick up the first directory given to us and make sure we know
+ where things should go"""
+ if os.path.isabs(self.conf.directory):
+ self.conf.basedir = os.path.dirname(self.conf.directory)
+ self.conf.relative_dir = os.path.basename(self.conf.directory)
+ else:
+ self.conf.basedir = os.path.realpath(self.conf.basedir)
+ self.conf.relative_dir = self.conf.directory
+
+ self.package_dir = os.path.join(self.conf.basedir,
+ self.conf.relative_dir)
+
+ if not self.conf.outputdir:
+ self.conf.outputdir = os.path.join(self.conf.basedir,
+ self.conf.relative_dir)
+
+ def _test_setup_dirs(self):
+ # start the sanity/stupidity checks
+ for mydir in self.conf.directories:
+ if os.path.isabs(mydir):
+ testdir = mydir
+ else:
+ if mydir.startswith('../'):
+ testdir = os.path.realpath(mydir)
+ else:
+ testdir = os.path.join(self.conf.basedir, mydir)
+
+ if not os.path.exists(testdir):
+ raise MDError, _('Directory %s must exist') % mydir
+
+ if not os.path.isdir(testdir):
+ raise MDError, _('%s must be a directory') % mydir
+
+ if not os.access(self.conf.outputdir, os.W_OK):
+ raise MDError, _('Directory %s must be writable.') % self.conf.outputdir
+
+ temp_output = os.path.join(self.conf.outputdir, self.conf.tempdir)
+ if not checkAndMakeDir(temp_output):
+ raise MDError, _('Cannot create/verify %s') % temp_output
+
+ temp_final = os.path.join(self.conf.outputdir, self.conf.finaldir)
+ if not checkAndMakeDir(temp_final):
+ raise MDError, _('Cannot create/verify %s') % temp_final
+
+ if self.conf.database:
+ # do flock test on temp_final, temp_output
+ # if it fails raise MDError
+ for direc in [temp_final, temp_output]:
+ f = open(direc + '/locktest', 'w')
+ try:
+ fcntl.flock(f.fileno(), fcntl.LOCK_EX)
+ except (OSError, IOError), e:
+ raise MDError, _("Could not create exclusive lock in %s and sqlite database generation enabled. Is this path on nfs? Is your lockd running?") % direc
+ else:
+ os.unlink(direc + '/locktest')
+
+ if self.conf.deltas:
+ temp_delta = os.path.join(self.conf.outputdir,
+ self.conf.delta_relative)
+ if not checkAndMakeDir(temp_delta):
+ raise MDError, _('Cannot create/verify %s') % temp_delta
+ self.conf.deltadir = temp_delta
+
+ if os.path.exists(os.path.join(self.conf.outputdir, self.conf.olddir)):
+ raise MDError, _('Old data directory exists, please remove: %s') % self.conf.olddir
+
+ # make sure we can write to where we want to write to:
+ # and pickup the mdtimestamps while we're at it
+ direcs = ['tempdir' , 'finaldir']
+ if self.conf.deltas:
+ direcs.append('deltadir')
+
+ for direc in direcs:
+ filepath = os.path.join(self.conf.outputdir, getattr(self.conf,
+ direc))
+ if os.path.exists(filepath):
+ if not os.access(filepath, os.W_OK):
+ raise MDError, _('error in must be able to write to metadata dir:\n -> %s') % filepath
+
+ if self.conf.checkts:
+ # checking for repodata/repomd.xml - not just the data dir
+ rxml = filepath + '/repomd.xml'
+ if os.path.exists(rxml):
+ timestamp = os.path.getctime(rxml)
+ if timestamp > self.conf.mdtimestamp:
+ self.conf.mdtimestamp = timestamp
+
+ if self.conf.groupfile:
+ a = self.conf.groupfile
+ if self.conf.split:
+ a = os.path.join(self.package_dir, self.conf.groupfile)
+ elif not os.path.isabs(a):
+ a = os.path.join(self.package_dir, self.conf.groupfile)
+
+ if not os.path.exists(a):
+ raise MDError, _('Error: groupfile %s cannot be found.' % a)
+
+ self.conf.groupfile = a
+
+ if self.conf.cachedir:
+ a = self.conf.cachedir
+ if not os.path.isabs(a):
+ a = os.path.join(self.conf.outputdir, a)
+ if not checkAndMakeDir(a):
+ raise MDError, _('Error: cannot open/write to cache dir %s' % a)
+
+ self.conf.cachedir = a
+
+
+ def _os_path_walk(self, top, func, arg):
+ """Directory tree walk with callback function.
+ copy of os.path.walk, fixes the link/stating problem
+ """
+
+ try:
+ names = os.listdir(top)
+ except os.error:
+ return
+ func(arg, top, names)
+ for name in names:
+ name = os.path.join(top, name)
+ if os.path.isdir(name):
+ self._os_path_walk(name, func, arg)
+ def getFileList(self, directory, ext):
+ """Return all files in path matching ext, store them in filelist,
+ recurse dirs. Returns a list object"""
+
+ extlen = len(ext)
+
+ def extension_visitor(filelist, dirname, names):
+ for fn in names:
+ if os.path.isdir(fn):
+ continue
+ if self.conf.skip_symlinks and os.path.islink(fn):
+ continue
+ elif fn[-extlen:].lower() == '%s' % (ext):
+ relativepath = dirname.replace(startdir, "", 1)
+ relativepath = relativepath.lstrip("/")
+ filelist.append(os.path.join(relativepath, fn))
+
+ filelist = []
+ startdir = directory + '/'
+ self._os_path_walk(startdir, extension_visitor, filelist)
+ return filelist
+
+ def errorlog(self, thing):
+ """subclass this if you want something different...."""
+ errorprint(thing)
+
+ def checkTimeStamps(self):
+ """check the timestamp of our target dir. If it is not newer than
+ the repodata return False, else True"""
+ if self.conf.checkts:
+ dn = os.path.join(self.conf.basedir, self.conf.directory)
+ files = self.getFileList(dn, '.rpm')
+ files = self.trimRpms(files)
+ for f in files:
+ fn = os.path.join(self.conf.basedir, self.conf.directory, f)
+ if not os.path.exists(fn):
+ self.callback.errorlog(_('cannot get to file: %s') % fn)
+ if os.path.getctime(fn) > self.conf.mdtimestamp:
+ return False
+
+ return True
+
+ return False
+
+ def trimRpms(self, files):
+ badrpms = []
+ for rpm_file in files:
+ for glob in self.conf.excludes:
+ if fnmatch.fnmatch(rpm_file, glob):
+ if rpm_file not in badrpms:
+ badrpms.append(rpm_file)
+ for rpm_file in badrpms:
+ if rpm_file in files:
+ files.remove(rpm_file)
+ return files
+
+ def _setup_old_metadata_lookup(self):
+ """sets up the .oldData object for handling the --update call. Speeds
+ up generating updates for new metadata"""
+ #FIXME - this only actually works for single dirs. It will only
+ # function for the first dir passed to --split, not all of them
+ # this needs to be fixed by some magic in readMetadata.py
+ # using opts.pkgdirs as a list, I think.
+ if self.conf.update:
+ #build the paths
+ opts = {
+ 'verbose' : self.conf.verbose,
+ 'pkgdir' : os.path.normpath(self.package_dir)
+ }
+
+ if self.conf.skip_stat:
+ opts['do_stat'] = False
+
+ if self.conf.update_md_path:
+ norm_u_md_path = os.path.normpath(self.conf.update_md_path)
+ u_md_repodata_path = norm_u_md_path + '/repodata'
+ if not os.path.exists(u_md_repodata_path):
+ msg = _('Warning: could not open update_md_path: %s') % u_md_repodata_path
+ self.callback.errorlog(msg)
+ old_repo_path = os.path.normpath(norm_u_md_path)
+ else:
+ old_repo_path = self.conf.outputdir
+
+ #and scan the old repo
+ self.oldData = readMetadata.MetadataIndex(old_repo_path, opts)
+
+ def _setup_grabber(self):
+ if not hasattr(self, '_grabber'):
+ self._grabber = grabber.URLGrabber()
+
+ return self._grabber
+
+ grabber = property(fget = lambda self: self._setup_grabber())
+
+
+ def doPkgMetadata(self):
+ """all the heavy lifting for the package metadata"""
+ if self.conf.update:
+ self._setup_old_metadata_lookup()
+ # rpms we're going to be dealing with
+ if self.conf.pkglist:
+ packages = self.conf.pkglist
+ else:
+ packages = self.getFileList(self.package_dir, '.rpm')
+
+ if not isinstance(packages, MetaSack):
+ packages = self.trimRpms(packages)
+ self.pkgcount = len(packages)
+ try:
+ self.openMetadataDocs()
+ self.writeMetadataDocs(packages)
+ self.closeMetadataDocs()
+ except (IOError, OSError), e:
+ raise MDError, _('Cannot access/write repodata files: %s') % e
+
+
+ def openMetadataDocs(self):
+ if self.conf.database_only:
+ self.setup_sqlite_dbs()
+ else:
+ self.primaryfile = self._setupPrimary()
+ self.flfile = self._setupFilelists()
+ self.otherfile = self._setupOther()
+ if self.conf.deltas:
+ self.deltafile = self._setupDelta()
+
+ def _setupPrimary(self):
+ # setup the primary metadata file
+ primaryfilepath = os.path.join(self.conf.outputdir, self.conf.tempdir,
+ self.conf.primaryfile)
+ fo = _gzipOpen(primaryfilepath, 'w')
+ fo.write('<?xml version="1.0" encoding="UTF-8"?>\n')
+ fo.write('<metadata xmlns="http://linux.duke.edu/metadata/common"' \
+ ' xmlns:rpm="http://linux.duke.edu/metadata/rpm" packages="%s">' %
+ self.pkgcount)
+ return fo
+
+ def _setupFilelists(self):
+ # setup the filelist file
+ filelistpath = os.path.join(self.conf.outputdir, self.conf.tempdir,
+ self.conf.filelistsfile)
+ fo = _gzipOpen(filelistpath, 'w')
+ fo.write('<?xml version="1.0" encoding="UTF-8"?>\n')
+ fo.write('<filelists xmlns="http://linux.duke.edu/metadata/filelists"' \
+ ' packages="%s">' % self.pkgcount)
+ return fo
+
+ def _setupOther(self):
+ # setup the other file
+ otherfilepath = os.path.join(self.conf.outputdir, self.conf.tempdir,
+ self.conf.otherfile)
+ fo = _gzipOpen(otherfilepath, 'w')
+ fo.write('<?xml version="1.0" encoding="UTF-8"?>\n')
+ fo.write('<otherdata xmlns="http://linux.duke.edu/metadata/other"' \
+ ' packages="%s">' %
+ self.pkgcount)
+ return fo
+
+ def _setupDelta(self):
+ # setup the other file
+ deltafilepath = os.path.join(self.conf.outputdir, self.conf.tempdir,
+ self.conf.deltafile)
+ fo = _gzipOpen(deltafilepath, 'w')
+ fo.write('<?xml version="1.0" encoding="UTF-8"?>\n')
+ fo.write('<prestodelta>\n')
+ return fo
+
+
+ def read_in_package(self, rpmfile, pkgpath=None, reldir=None):
+ """rpmfile == relative path to file from self.packge_dir"""
+ baseurl = self.conf.baseurl
+
+ if not pkgpath:
+ pkgpath = self.package_dir
+
+ if not rpmfile.strip():
+ raise MDError, "Blank filename passed in, skipping"
+
+ if rpmfile.find("://") != -1:
+
+ if not hasattr(self, 'tempdir'):
+ self.tempdir = tempfile.mkdtemp()
+
+ pkgname = os.path.basename(rpmfile)
+ baseurl = os.path.dirname(rpmfile)
+ reldir = self.tempdir
+ dest = os.path.join(self.tempdir, pkgname)
+ if not self.conf.quiet:
+ self.callback.log('\nDownloading %s' % rpmfile)
+ try:
+ rpmfile = self.grabber.urlgrab(rpmfile, dest)
+ except grabber.URLGrabError, e:
+ raise MDError, "Unable to retrieve remote package %s: %s" % (
+ rpmfile, e)
+
+
+ else:
+ rpmfile = '%s/%s' % (pkgpath, rpmfile)
+
+ external_data = { '_cachedir': self.conf.cachedir,
+ '_baseurl': baseurl,
+ '_reldir': reldir,
+ '_packagenumber': self.current_pkg,
+ '_collapse_libc_requires':self.conf.collapse_glibc_requires,
+ }
+
+ try:
+ po = yumbased.CreateRepoPackage(self.ts, rpmfile,
+ sumtype=self.conf.sumtype,
+ external_data = external_data)
+ except Errors.MiscError, e:
+ raise MDError, "Unable to open package: %s" % e
+
+ for r in po.requires_print:
+ if r.startswith('rpmlib('):
+ self.rpmlib_reqs[r] = 1
+
+ if po.checksum in (None, ""):
+ raise MDError, "No Package ID found for package %s, not going to" \
+ " add it" % po
+
+ return po
+
+ def writeMetadataDocs(self, pkglist=[], pkgpath=None):
+
+ if not pkglist:
+ pkglist = self.conf.pkglist
+
+ if not pkgpath:
+ directory = self.conf.directory
+ else:
+ directory = pkgpath
+
+ # for worker/forked model
+ # iterate the pkglist - see which ones are handled by --update and let them
+ # go on their merry way
+
+ newpkgs = []
+ if self.conf.update:
+ # if we're in --update mode then only act on the new/changed pkgs
+ for pkg in pkglist:
+ self.current_pkg += 1
+
+ #see if we can pull the nodes from the old repo
+ #print self.oldData.basenodes.keys()
+ old_pkg = pkg
+ if pkg.find("://") != -1:
+ old_pkg = os.path.basename(pkg)
+ nodes = self.oldData.getNodes(old_pkg)
+ if nodes is not None: # we have a match in the old metadata
+ if self.conf.verbose:
+ self.callback.log(_("Using data from old metadata for %s")
+ % pkg)
+ (primarynode, filenode, othernode) = nodes
+
+ for node, outfile in ((primarynode, self.primaryfile),
+ (filenode, self.flfile),
+ (othernode, self.otherfile)):
+ if node is None:
+ break
+
+ if self.conf.baseurl:
+ anode = node.children
+ while anode is not None:
+ if anode.type != "element":
+ anode = anode.next
+ continue
+ if anode.name == "location":
+ anode.setProp('xml:base', self.conf.baseurl)
+ anode = anode.next
+
+ output = node.serialize('UTF-8', self.conf.pretty)
+ if output:
+ outfile.write(output)
+ else:
+ if self.conf.verbose:
+ self.callback.log(_("empty serialize on write to" \
+ "%s in %s") % (outfile, pkg))
+ outfile.write('\n')
+
+ self.oldData.freeNodes(pkg)
+ #FIXME - if we're in update and we have deltas enabled
+ # check the presto data for this pkg and write its info back out
+ # to our deltafile
+ continue
+ else:
+ newpkgs.append(pkg)
+ else:
+ newpkgs = pkglist
+
+ # setup our reldir
+ if not pkgpath:
+ reldir = os.path.join(self.conf.basedir, directory)
+ else:
+ reldir = pkgpath
+
+ # filter out those pkgs which are not files - but are pkgobjects
+ pkgfiles = []
+ for pkg in newpkgs:
+ po = None
+ if isinstance(pkg, YumAvailablePackage):
+ po = pkg
+ self.read_pkgs.append(po.localpath)
+
+ # if we're dealing with remote pkgs - pitch it over to doing
+ # them one at a time, for now.
+ elif pkg.find('://') != -1:
+ po = self.read_in_package(pkgfile, pkgpath=pkgpath, reldir=reldir)
+ self.read_pkgs.append(pkg)
+
+ if po:
+ self.primaryfile.write(po.xml_dump_primary_metadata())
+ self.flfile.write(po.xml_dump_filelists_metadata())
+ self.otherfile.write(po.xml_dump_other_metadata(
+ clog_limit=self.conf.changelog_limit))
+ continue
+
+ pkgfiles.append(pkg)
+
+
+ if pkgfiles:
+ # divide that list by the number of workers and fork off that many
+ # workers to tmpdirs
+ # waitfor the workers to finish and as each one comes in
+ # open the files they created and write them out to our metadata
+ # add up the total pkg counts and return that value
+ worker_tmp_path = tempfile.mkdtemp()
+ worker_chunks = utils.split_list_into_equal_chunks(pkgfiles, self.conf.workers)
+ worker_cmd_dict = {}
+ worker_jobs = {}
+ base_worker_cmdline = [self.conf.worker_cmd,
+ '--pkgoptions=_reldir=%s' % reldir,
+ '--pkgoptions=_collapse_libc_requires=%s' % self.conf.collapse_glibc_requires,
+ '--pkgoptions=_cachedir=%s' % self.conf.cachedir,
+ '--pkgoptions=_baseurl=%s' % self.conf.baseurl,
+ '--globalopts=clog_limit=%s' % self.conf.changelog_limit,]
+
+ if self.conf.quiet:
+ base_worker_cmdline.append('--quiet')
+
+ if self.conf.verbose:
+ base_worker_cmdline.append('--verbose')
+
+ for worker_num in range(self.conf.workers):
+ # make the worker directory
+ workercmdline = []
+ workercmdline.extend(base_worker_cmdline)
+ thisdir = worker_tmp_path + '/' + str(worker_num)
+ if checkAndMakeDir(thisdir):
+ workercmdline.append('--tmpmdpath=%s' % thisdir)
+ else:
+ raise MDError, "Unable to create worker path: %s" % thisdir
+ workercmdline.extend(worker_chunks[worker_num])
+ worker_cmd_dict[worker_num] = workercmdline
+
+
+
+ for (num, cmdline) in worker_cmd_dict.items():
+ if not self.conf.quiet:
+ self.callback.log("Spawning worker %s with %s pkgs" % (num,
+ len(worker_chunks[num])))
+ job = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ worker_jobs[num] = job
+
+ gimmebreak = 0
+ while gimmebreak != len(worker_jobs.keys()):
+ gimmebreak = 0
+ for (num,job) in worker_jobs.items():
+ if job.poll() is not None:
+ gimmebreak+=1
+ line = job.stdout.readline()
+ if line:
+ self.callback.log('Worker %s: %s' % (num, line.rstrip()))
+ line = job.stderr.readline()
+ if line:
+ self.callback.errorlog('Worker %s: %s' % (num, line.rstrip()))
+
+
+ if not self.conf.quiet:
+ self.callback.log("Workers Finished")
+ # finished with workers
+ # go to their dirs and add the contents
+ if not self.conf.quiet:
+ self.callback.log("Gathering worker results")
+ for num in range(self.conf.workers):
+ for (fn, fo) in (('primary.xml', self.primaryfile),
+ ('filelists.xml', self.flfile),
+ ('other.xml', self.otherfile)):
+ fnpath = worker_tmp_path + '/' + str(num) + '/' + fn
+ if os.path.exists(fnpath):
+ fo.write(open(fnpath, 'r').read())
+
+
+ for pkgfile in pkgfiles:
+ if self.conf.deltas:
+ po = self.read_in_package(pkgfile, pkgpath=pkgpath, reldir=reldir)
+ self._do_delta_rpm_package(po)
+ self.read_pkgs.append(pkgfile)
+
+ return self.current_pkg
+
+
+ def closeMetadataDocs(self):
+ if not self.conf.quiet:
+ self.callback.log('')
+
+
+ # save them up to the tmp locations:
+ if not self.conf.quiet:
+ self.callback.log(_('Saving Primary metadata'))
+ if self.conf.database_only:
+ self.md_sqlite.pri_cx.close()
+ else:
+ self.primaryfile.write('\n</metadata>')
+ self.primaryfile.close()
+
+ if not self.conf.quiet:
+ self.callback.log(_('Saving file lists metadata'))
+ if self.conf.database_only:
+ self.md_sqlite.file_cx.close()
+ else:
+ self.flfile.write('\n</filelists>')
+ self.flfile.close()
+
+ if not self.conf.quiet:
+ self.callback.log(_('Saving other metadata'))
+ if self.conf.database_only:
+ self.md_sqlite.other_cx.close()
+ else:
+ self.otherfile.write('\n</otherdata>')
+ self.otherfile.close()
+
+ if self.conf.deltas:
+ deltam_st = time.time()
+ if not self.conf.quiet:
+ self.callback.log(_('Saving delta metadata'))
+ self.deltafile.write(self.generate_delta_xml())
+ self.deltafile.write('\n</prestodelta>')
+ self.deltafile.close()
+ if self.conf.profile:
+ self.callback.log('deltam time: %0.3f' % (time.time() - deltam_st))
+
+ def _do_delta_rpm_package(self, pkg):
+ """makes the drpms, if possible, for this package object.
+ returns the presto/delta xml metadata as a string
+ """
+ drpm_pkg_time = time.time()
+ # duck and cover if the pkg.size is > whatever
+ if int(pkg.size) > self.conf.max_delta_rpm_size:
+ if not self.conf.quiet:
+ self.callback.log("Skipping %s package " \
+ "that is > max_delta_rpm_size" % pkg)
+ return
+
+ # generate a list of all the potential 'old rpms'
+ opd = self._get_old_package_dict()
+ # for each of our old_package_paths -
+ # make a drpm from the newest of that pkg
+ # get list of potential candidates which are likely to match
+ for d in self.conf.oldpackage_paths:
+ pot_cand = []
+ if d not in opd:
+ continue
+ for fn in opd[d]:
+ if os.path.basename(fn).startswith(pkg.name):
+ pot_cand.append(fn)
+
+ candidates = []
+ for fn in pot_cand:
+ try:
+ thispo = yumbased.CreateRepoPackage(self.ts, fn,
+ sumtype=self.conf.sumtype)
+ except Errors.MiscError, e:
+ continue
+ if (thispo.name, thispo.arch) != (pkg.name, pkg.arch):
+ # not the same, doesn't matter
+ continue
+ if thispo == pkg: #exactly the same, doesn't matter
+ continue
+ if thispo.EVR >= pkg.EVR: # greater or equal, doesn't matter
+ continue
+ candidates.append(thispo)
+ candidates.sort()
+ candidates.reverse()
+
+ for delta_p in candidates[0:self.conf.num_deltas]:
+ #make drpm of pkg and delta_p
+ dt_st = time.time()
+ drpmfn = deltarpms.create_drpm(delta_p, pkg, self.conf.deltadir)
+ if not self.conf.quiet or self.conf.profile:
+ self.callback.log('created drpm from %s to %s: %s in %0.3f' % (
+ delta_p, pkg, drpmfn, (time.time() - dt_st)))
+ if self.conf.profile:
+ self.callback.log('total drpm time for %s: %0.3f' % (pkg,
+ (time.time() - drpm_pkg_time)))
+
+ def _get_old_package_dict(self):
+ if hasattr(self, '_old_package_dict'):
+ return self._old_package_dict
+
+ self._old_package_dict = {}
+ opl = []
+ for d in self.conf.oldpackage_paths:
+ for f in self.getFileList(d, '.rpm'):
+ fp = d + '/' + f
+ fpstat = os.stat(fp)
+ if int(fpstat[stat.ST_SIZE]) > self.conf.max_delta_rpm_size:
+ self.callback.log("Skipping %s package " \
+ "that is > max_delta_rpm_size" % f)
+ continue
+ if not self._old_package_dict.has_key(d):
+ self._old_package_dict[d] = []
+ self._old_package_dict[d].append(d + '/' + f)
+
+ return self._old_package_dict
+
+ def generate_delta_xml(self):
+ """take the delta rpm output dir, process all the drpm files
+ produce the text output for the presto/delta xml metadata"""
+ # go through the drpm dir
+ # for each file -store the drpm info in a dict based on its target. Just
+ # appending the output. for each of the keys in the dict, return
+ # the tag for the target + each of the drpm infos + closure for the target
+ # tag
+ targets = {}
+ results = []
+ for drpm_fn in self.getFileList(self.conf.deltadir, '.drpm'):
+ drpm_rel_fn = os.path.normpath(self.conf.delta_relative +
+ '/' + drpm_fn) # this is annoying
+ drpm_po = yumbased.CreateRepoPackage(self.ts,
+ self.conf.deltadir + '/' + drpm_fn, sumtype=self.conf.sumtype)
+
+ drpm = deltarpms.DeltaRPMPackage(drpm_po, self.conf.outputdir,
+ drpm_rel_fn)
+ if not targets.has_key(drpm_po.pkgtup):
+ targets[drpm_po.pkgtup] = []
+ targets[drpm_po.pkgtup].append(drpm.xml_dump_metadata())
+
+ for (n, a, e, v, r) in targets.keys():
+ results.append(""" <newpackage name="%s" epoch="%s" version="%s" release="%s" arch="%s">\n""" % (
+ n, e, v, r, a))
+ results.extend(targets[(n,a,e,v,r)])
+# for src in targets[(n, a, e, v, r)]:
+# results.append(src)
+
+ results.append(" </newpackage>\n")
+
+ return ' '.join(results)
+
+ def _createRepoDataObject(self, mdfile, mdtype, compress=True,
+ compress_type='gzip', attribs={}):
+ """return random metadata as RepoData object to be added to RepoMD
+ mdfile = complete path to file
+ mdtype = the metadata type to use
+ compress = compress the file before including it
+ """
+ # copy the file over here
+ sfile = os.path.basename(mdfile)
+ fo = open(mdfile, 'r')
+ outdir = os.path.join(self.conf.outputdir, self.conf.tempdir)
+ if compress:
+ if compress_type == 'gzip':
+ sfile = '%s.gz' % sfile
+ outfn = os.path.join(outdir, sfile)
+ output = GzipFile(filename = outfn, mode='wb')
+ elif compress_type == 'bzip2':
+ sfile = '%s.bz2' % sfile
+ outfn = os.path.join(outdir, sfile)
+ output = BZ2File(filename = outfn, mode='wb')
+ else:
+ outfn = os.path.join(outdir, sfile)
+ output = open(outfn, 'w')
+
+ output.write(fo.read())
+ output.close()
+ fo.seek(0)
+ open_csum = misc.checksum(self.conf.sumtype, fo)
+ fo.close()
+
+
+ if self.conf.unique_md_filenames:
+ (csum, outfn) = checksum_and_rename(outfn, self.conf.sumtype)
+ sfile = os.path.basename(outfn)
+ else:
+ if compress:
+ csum = misc.checksum(self.conf.sumtype, outfn)
+ else:
+ csum = open_csum
+
+ thisdata = RepoData()
+ thisdata.type = mdtype
+ baseloc = None
+ thisdata.location = (self.conf.baseurl, os.path.join(self.conf.finaldir, sfile))
+ thisdata.checksum = (self.conf.sumtype, csum)
+ if compress:
+ thisdata.openchecksum = (self.conf.sumtype, open_csum)
+
+ thisdata.size = str(os.stat(outfn).st_size)
+ thisdata.timestamp = str(os.stat(outfn).st_mtime)
+ for (k, v) in attribs.items():
+ setattr(thisdata, k, str(v))
+
+ return thisdata
+
+
+ def doRepoMetadata(self):
+ """wrapper to generate the repomd.xml file that stores the info
+ on the other files"""
+
+ repomd = RepoMD('repoid')
+ repomd.revision = self.conf.revision
+
+ repopath = os.path.join(self.conf.outputdir, self.conf.tempdir)
+ repofilepath = os.path.join(repopath, self.conf.repomdfile)
+
+ if self.conf.content_tags:
+ repomd.tags['content'] = self.conf.content_tags
+ if self.conf.distro_tags:
+ repomd.tags['distro'] = self.conf.distro_tags
+ # NOTE - test out the cpeid silliness here
+ if self.conf.repo_tags:
+ repomd.tags['repo'] = self.conf.repo_tags
+
+
+ sumtype = self.conf.sumtype
+ workfiles = [(self.conf.otherfile, 'other',),
+ (self.conf.filelistsfile, 'filelists'),
+ (self.conf.primaryfile, 'primary')]
+
+ if self.conf.deltas:
+ workfiles.append((self.conf.deltafile, 'prestodelta'))
+
+ if self.conf.database:
+ if not self.conf.quiet: self.callback.log('Generating sqlite DBs')
+ try:
+ dbversion = str(sqlitecachec.DBVERSION)
+ except AttributeError:
+ dbversion = '9'
+ #FIXME - in theory some sort of try/except here
+ rp = sqlitecachec.RepodataParserSqlite(repopath, repomd.repoid, None)
+
+ for (rpm_file, ftype) in workfiles:
+ complete_path = os.path.join(repopath, rpm_file)
+
+ zfo = _gzipOpen(complete_path)
+ # This is misc.checksum() done locally so we can get the size too.
+ data = misc.Checksums([sumtype])
+ while data.read(zfo, 2**16):
+ pass
+ uncsum = data.hexdigest(sumtype)
+ unsize = len(data)
+ zfo.close()
+ csum = misc.checksum(sumtype, complete_path)
+ timestamp = os.stat(complete_path)[8]
+
+ db_csums = {}
+ db_compressed_sums = {}
+
+ if self.conf.database:
+ if ftype in ['primary', 'filelists', 'other']:
+ if self.conf.verbose:
+ self.callback.log("Starting %s db creation: %s" % (ftype,
+ time.ctime()))
+
+ if ftype == 'primary':
+ #FIXME - in theory some sort of try/except here
+ # TypeError appears to be raised, sometimes :(
+ rp.getPrimary(complete_path, csum)
+
+ elif ftype == 'filelists':
+ #FIXME and here
+ rp.getFilelists(complete_path, csum)
+
+ elif ftype == 'other':
+ #FIXME and here
+ rp.getOtherdata(complete_path, csum)
+
+ if ftype in ['primary', 'filelists', 'other']:
+ tmp_result_name = '%s.xml.gz.sqlite' % ftype
+ tmp_result_path = os.path.join(repopath, tmp_result_name)
+ good_name = '%s.sqlite' % ftype
+ resultpath = os.path.join(repopath, good_name)
+
+ # rename from silly name to not silly name
+ os.rename(tmp_result_path, resultpath)
+ compressed_name = '%s.bz2' % good_name
+ result_compressed = os.path.join(repopath, compressed_name)
+ db_csums[ftype] = misc.checksum(sumtype, resultpath)
+
+ # compress the files
+ bzipFile(resultpath, result_compressed)
+ # csum the compressed file
+ db_compressed_sums[ftype] = misc.checksum(sumtype,
+ result_compressed)
+ # timestamp+size the uncompressed file
+ un_stat = os.stat(resultpath)
+ # remove the uncompressed file
+ os.unlink(resultpath)
+
+ if self.conf.unique_md_filenames:
+ csum_compressed_name = '%s-%s.bz2' % (
+ db_compressed_sums[ftype], good_name)
+ csum_result_compressed = os.path.join(repopath,
+ csum_compressed_name)
+ os.rename(result_compressed, csum_result_compressed)
+ result_compressed = csum_result_compressed
+ compressed_name = csum_compressed_name
+
+ # timestamp+size the compressed file
+ db_stat = os.stat(result_compressed)
+
+ # add this data as a section to the repomdxml
+ db_data_type = '%s_db' % ftype
+ data = RepoData()
+ data.type = db_data_type
+ data.location = (self.conf.baseurl,
+ os.path.join(self.conf.finaldir, compressed_name))
+ data.checksum = (sumtype, db_compressed_sums[ftype])
+ data.timestamp = str(db_stat.st_mtime)
+ data.size = str(db_stat.st_size)
+ data.opensize = str(un_stat.st_size)
+ data.openchecksum = (sumtype, db_csums[ftype])
+ data.dbversion = dbversion
+ if self.conf.verbose:
+ self.callback.log("Ending %s db creation: %s" % (ftype,
+ time.ctime()))
+ repomd.repoData[data.type] = data
+
+ data = RepoData()
+ data.type = ftype
+ data.checksum = (sumtype, csum)
+ data.timestamp = str(timestamp)
+ data.size = str(os.stat(os.path.join(repopath, rpm_file)).st_size)
+ data.opensize = str(unsize)
+ data.openchecksum = (sumtype, uncsum)
+
+ if self.conf.unique_md_filenames:
+ res_file = '%s-%s.xml.gz' % (csum, ftype)
+ orig_file = os.path.join(repopath, rpm_file)
+ dest_file = os.path.join(repopath, res_file)
+ os.rename(orig_file, dest_file)
+ else:
+ res_file = rpm_file
+ rpm_file = res_file
+ href = os.path.join(self.conf.finaldir, rpm_file)
+
+ data.location = (self.conf.baseurl, href)
+ repomd.repoData[data.type] = data
+
+ if not self.conf.quiet and self.conf.database:
+ self.callback.log('Sqlite DBs complete')
+
+
+ if self.conf.groupfile is not None:
+ mdcontent = self._createRepoDataObject(self.conf.groupfile, 'group_gz')
+ repomd.repoData[mdcontent.type] = mdcontent
+
+ mdcontent = self._createRepoDataObject(self.conf.groupfile, 'group',
+ compress=False)
+ repomd.repoData[mdcontent.type] = mdcontent
+
+
+ if self.conf.additional_metadata:
+ for md_type, mdfile in self.conf.additional_metadata.items():
+ mdcontent = self._createRepoDataObject(md_file, md_type)
+ repomd.repoData[mdcontent.type] = mdcontent
+
+
+ # FIXME - disabled until we decide how best to use this
+ #if self.rpmlib_reqs:
+ # rpmlib = reporoot.newChild(rpmns, 'lib', None)
+ # for r in self.rpmlib_reqs.keys():
+ # req = rpmlib.newChild(rpmns, 'requires', r)
+
+
+ # save it down
+ try:
+ fo = open(repofilepath, 'w')
+ fo.write(repomd.dump_xml())
+ fo.close()
+ except (IOError, OSError, TypeError), e:
+ self.callback.errorlog(
+ _('Error saving temp file for repomd.xml: %s') % repofilepath)
+ self.callback.errorlog('Error was: %s') % str(e)
+ fo.close()
+ raise MDError, 'Could not save temp file: %s' % repofilepath
+
+
+ def doFinalMove(self):
+ """move the just-created repodata from .repodata to repodata
+ also make sure to preserve any files we didn't mess with in the
+ metadata dir"""
+
+ output_final_dir = os.path.join(self.conf.outputdir, self.conf.finaldir)
+ output_old_dir = os.path.join(self.conf.outputdir, self.conf.olddir)
+
+ if os.path.exists(output_final_dir):
+ try:
+ os.rename(output_final_dir, output_old_dir)
+ except:
+ raise MDError, _('Error moving final %s to old dir %s' % (
+ output_final_dir, output_old_dir))
+
+ output_temp_dir = os.path.join(self.conf.outputdir, self.conf.tempdir)
+
+ try:
+ os.rename(output_temp_dir, output_final_dir)
+ except:
+ # put the old stuff back
+ os.rename(output_old_dir, output_final_dir)
+ raise MDError, _('Error moving final metadata into place')
+
+ for f in ['primaryfile', 'filelistsfile', 'otherfile', 'repomdfile',
+ 'groupfile']:
+ if getattr(self.conf, f):
+ fn = os.path.basename(getattr(self.conf, f))
+ else:
+ continue
+ oldfile = os.path.join(output_old_dir, fn)
+
+ if os.path.exists(oldfile):
+ try:
+ os.remove(oldfile)
+ except OSError, e:
+ raise MDError, _(
+ 'Could not remove old metadata file: %s: %s') % (oldfile, e)
+
+ # Move everything else back from olddir (eg. repoview files)
+ try:
+ old_contents = os.listdir(output_old_dir)
+ except (OSError, IOError), e:
+ old_contents = []
+
+ for f in os.listdir(output_old_dir):
+ oldfile = os.path.join(output_old_dir, f)
+ finalfile = os.path.join(output_final_dir, f)
+ if f.find('-') != -1 and f.split('-')[1] in ('primary.sqlite.bz2',
+ 'filelists.sqlite.bz2', 'primary.xml.gz','other.sqlite.bz2',
+ 'other.xml.gz','filelists.xml.gz'):
+ os.remove(oldfile) # kill off the old ones
+ continue
+ if f in ('filelists.sqlite.bz2', 'other.sqlite.bz2',
+ 'primary.sqlite.bz2'):
+ os.remove(oldfile)
+ continue
+
+ if os.path.exists(finalfile):
+ # Hmph? Just leave it alone, then.
+ try:
+ if os.path.isdir(oldfile):
+ shutil.rmtree(oldfile)
+ else:
+ os.remove(oldfile)
+ except OSError, e:
+ raise MDError, _(
+ 'Could not remove old metadata file: %s: %s') % (oldfile, e)
+ else:
+ try:
+ os.rename(oldfile, finalfile)
+ except OSError, e:
+ msg = _('Could not restore old non-metadata file: %s -> %s') % (oldfile, finalfile)
+ msg += _('Error was %s') % e
+ raise MDError, msg
+
+ try:
+ os.rmdir(output_old_dir)
+ except OSError, e:
+ self.errorlog(_('Could not remove old metadata dir: %s')
+ % self.conf.olddir)
+ self.errorlog(_('Error was %s') % e)
+ self.errorlog(_('Please clean up this directory manually.'))
+
+ # write out the read_pkgs_list file with self.read_pkgs
+ if self.conf.read_pkgs_list:
+ try:
+ fo = open(self.conf.read_pkgs_list, 'w')
+ fo.write('\n'.join(self.read_pkgs))
+ fo.flush()
+ fo.close()
+ except (OSError, IOError), e:
+ self.errorlog(_('Could not write out readpkgs list: %s')
+ % self.conf.read_pkgs_list)
+ self.errorlog(_('Error was %s') % e)
+
+ def setup_sqlite_dbs(self, initdb=True):
+ """sets up the sqlite dbs w/table schemas and db_infos"""
+ destdir = os.path.join(self.conf.outputdir, self.conf.tempdir)
+ try:
+ self.md_sqlite = MetaDataSqlite(destdir)
+ except sqlite.OperationalError, e:
+ raise MDError, _('Cannot create sqlite databases: %s.\n'\
+ 'Maybe you need to clean up a .repodata dir?') % e
+
+
+
+class SplitMetaDataGenerator(MetaDataGenerator):
+ """takes a series of dirs and creates repodata for all of them
+ most commonly used with -u media:// - if no outputdir is specified
+ it will create the repodata in the first dir in the list of dirs
+ """
+ def __init__(self, config_obj=None, callback=None):
+ MetaDataGenerator.__init__(self, config_obj=config_obj, callback=None)
+
+ def _getFragmentUrl(self, url, fragment):
+ import urlparse
+ urlparse.uses_fragment.append('media')
+ if not url:
+ return url
+ (scheme, netloc, path, query, fragid) = urlparse.urlsplit(url)
+ return urlparse.urlunsplit((scheme, netloc, path, query, str(fragment)))
+
+ def getFileList(self, directory, ext):
+
+ extlen = len(ext)
+
+ def extension_visitor(arg, dirname, names):
+ for fn in names:
+ if os.path.isdir(fn):
+ continue
+ elif fn[-extlen:].lower() == '%s' % (ext):
+ reldir = os.path.basename(dirname)
+ if reldir == os.path.basename(directory):
+ reldir = ""
+ arg.append(os.path.join(reldir, fn))
+
+ rpmlist = []
+ os.path.walk(directory, extension_visitor, rpmlist)
+ return rpmlist
+
+ def doPkgMetadata(self):
+ """all the heavy lifting for the package metadata"""
+ if len(self.conf.directories) == 1:
+ MetaDataGenerator.doPkgMetadata(self)
+ return
+
+ if self.conf.update:
+ self._setup_old_metadata_lookup()
+
+ filematrix = {}
+ for mydir in self.conf.directories:
+ if os.path.isabs(mydir):
+ thisdir = mydir
+ else:
+ if mydir.startswith('../'):
+ thisdir = os.path.realpath(mydir)
+ else:
+ thisdir = os.path.join(self.conf.basedir, mydir)
+
+ filematrix[mydir] = self.getFileList(thisdir, '.rpm')
+ self.trimRpms(filematrix[mydir])
+ self.pkgcount += len(filematrix[mydir])
+
+ mediano = 1
+ self.current_pkg = 0
+ self.conf.baseurl = self._getFragmentUrl(self.conf.baseurl, mediano)
+ try:
+ self.openMetadataDocs()
+ original_basedir = self.conf.basedir
+ for mydir in self.conf.directories:
+ self.conf.baseurl = self._getFragmentUrl(self.conf.baseurl, mediano)
+ self.writeMetadataDocs(filematrix[mydir], mydir)
+ mediano += 1
+ self.conf.baseurl = self._getFragmentUrl(self.conf.baseurl, 1)
+ self.closeMetadataDocs()
+ except (IOError, OSError), e:
+ raise MDError, _('Cannot access/write repodata files: %s') % e
+
+
+
+class MetaDataSqlite(object):
+ def __init__(self, destdir):
+ self.pri_sqlite_file = os.path.join(destdir, 'primary.sqlite')
+ self.pri_cx = sqlite.Connection(self.pri_sqlite_file)
+ self.file_sqlite_file = os.path.join(destdir, 'filelists.sqlite')
+ self.file_cx = sqlite.Connection(self.file_sqlite_file)
+ self.other_sqlite_file = os.path.join(destdir, 'other.sqlite')
+ self.other_cx = sqlite.Connection(self.other_sqlite_file)
+ self.primary_cursor = self.pri_cx.cursor()
+
+ self.filelists_cursor = self.file_cx.cursor()
+
+ self.other_cursor = self.other_cx.cursor()
+
+ self.create_primary_db()
+ self.create_filelists_db()
+ self.create_other_db()
+
+ def create_primary_db(self):
+ # make the tables
+ schema = [
+ """PRAGMA synchronous="OFF";""",
+ """pragma locking_mode="EXCLUSIVE";""",
+ """CREATE TABLE conflicts ( name TEXT, flags TEXT, epoch TEXT, version TEXT, release TEXT, pkgKey INTEGER );""",
+ """CREATE TABLE db_info (dbversion INTEGER, checksum TEXT);""",
+ """CREATE TABLE files ( name TEXT, type TEXT, pkgKey INTEGER);""",
+ """CREATE TABLE obsoletes ( name TEXT, flags TEXT, epoch TEXT, version TEXT, release TEXT, pkgKey INTEGER );""",
+ """CREATE TABLE packages ( pkgKey INTEGER PRIMARY KEY, pkgId TEXT, name TEXT, arch TEXT, version TEXT, epoch TEXT, release TEXT, summary TEXT, description TEXT, url TEXT, time_file INTEGER, time_build INTEGER, rpm_license TEXT, rpm_vendor TEXT, rpm_group TEXT, rpm_buildhost TEXT, rpm_sourcerpm TEXT, rpm_header_start INTEGER, rpm_header_end INTEGER, rpm_packager TEXT, size_package INTEGER, size_installed INTEGER, size_archive INTEGER, location_href TEXT, location_base TEXT, checksum_type TEXT);""",
+ """CREATE TABLE provides ( name TEXT, flags TEXT, epoch TEXT, version TEXT, release TEXT, pkgKey INTEGER );""",
+ """CREATE TABLE requires ( name TEXT, flags TEXT, epoch TEXT, version TEXT, release TEXT, pkgKey INTEGER , pre BOOL DEFAULT FALSE);""",
+ """CREATE INDEX filenames ON files (name);""",
+ """CREATE INDEX packageId ON packages (pkgId);""",
+ """CREATE INDEX packagename ON packages (name);""",
+ """CREATE INDEX pkgconflicts on conflicts (pkgKey);""",
+ """CREATE INDEX pkgobsoletes on obsoletes (pkgKey);""",
+ """CREATE INDEX pkgprovides on provides (pkgKey);""",
+ """CREATE INDEX pkgrequires on requires (pkgKey);""",
+ """CREATE INDEX providesname ON provides (name);""",
+ """CREATE INDEX requiresname ON requires (name);""",
+ """CREATE TRIGGER removals AFTER DELETE ON packages
+ BEGIN
+ DELETE FROM files WHERE pkgKey = old.pkgKey;
+ DELETE FROM requires WHERE pkgKey = old.pkgKey;
+ DELETE FROM provides WHERE pkgKey = old.pkgKey;
+ DELETE FROM conflicts WHERE pkgKey = old.pkgKey;
+ DELETE FROM obsoletes WHERE pkgKey = old.pkgKey;
+ END;""",
+ """INSERT into db_info values (%s, 'direct_create');""" % sqlitecachec.DBVERSION,
+ ]
+
+ for cmd in schema:
+ executeSQL(self.primary_cursor, cmd)
+
+ def create_filelists_db(self):
+ schema = [
+ """PRAGMA synchronous="OFF";""",
+ """pragma locking_mode="EXCLUSIVE";""",
+ """CREATE TABLE db_info (dbversion INTEGER, checksum TEXT);""",
+ """CREATE TABLE filelist ( pkgKey INTEGER, dirname TEXT, filenames TEXT, filetypes TEXT);""",
+ """CREATE TABLE packages ( pkgKey INTEGER PRIMARY KEY, pkgId TEXT);""",
+ """CREATE INDEX dirnames ON filelist (dirname);""",
+ """CREATE INDEX keyfile ON filelist (pkgKey);""",
+ """CREATE INDEX pkgId ON packages (pkgId);""",
+ """CREATE TRIGGER remove_filelist AFTER DELETE ON packages
+ BEGIN
+ DELETE FROM filelist WHERE pkgKey = old.pkgKey;
+ END;""",
+ """INSERT into db_info values (%s, 'direct_create');""" % sqlitecachec.DBVERSION,
+ ]
+ for cmd in schema:
+ executeSQL(self.filelists_cursor, cmd)
+
+ def create_other_db(self):
+ schema = [
+ """PRAGMA synchronous="OFF";""",
+ """pragma locking_mode="EXCLUSIVE";""",
+ """CREATE TABLE changelog ( pkgKey INTEGER, author TEXT, date INTEGER, changelog TEXT);""",
+ """CREATE TABLE db_info (dbversion INTEGER, checksum TEXT);""",
+ """CREATE TABLE packages ( pkgKey INTEGER PRIMARY KEY, pkgId TEXT);""",
+ """CREATE INDEX keychange ON changelog (pkgKey);""",
+ """CREATE INDEX pkgId ON packages (pkgId);""",
+ """CREATE TRIGGER remove_changelogs AFTER DELETE ON packages
+ BEGIN
+ DELETE FROM changelog WHERE pkgKey = old.pkgKey;
+ END;""",
+ """INSERT into db_info values (%s, 'direct_create');""" % sqlitecachec.DBVERSION,
+ ]
+
+ for cmd in schema:
+ executeSQL(self.other_cursor, cmd)
diff --git a/createrepo/deltarpms.py b/createrepo/deltarpms.py
new file mode 100644
index 0000000..3edcbb5
--- /dev/null
+++ b/createrepo/deltarpms.py
@@ -0,0 +1,124 @@
+#!/usr/bin/python -tt
+# util functions for deltarpms
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# copyright 2009 - Red Hat
+
+import os.path
+import commands
+from yum import misc
+import deltarpm
+from utils import MDError
+
+class DeltaRPMPackage:
+ """each drpm is one object, you pass it a drpm file
+ it opens the file, and pulls the information out in bite-sized chunks :)
+ """
+
+ mode_cache = {}
+
+ def __init__(self, po, basedir, filename):
+ try:
+ stats = os.stat(os.path.join(basedir, filename))
+ self.size = stats[6]
+ self.mtime = stats[8]
+ del stats
+ except OSError, e:
+ raise MDError, "Error Stat'ing file %s%s" % (basedir, filename)
+ self.csum_type = 'sha256'
+ self.relativepath = filename
+ self.po = po
+
+ fd = os.open(self.po.localpath, os.O_RDONLY)
+ os.lseek(fd, 0, 0)
+ fo = os.fdopen(fd, 'rb')
+ self.csum = misc.checksum(self.csum_type, fo)
+ del fo
+ del fd
+ self._getDRPMInfo(os.path.join(basedir, filename))
+
+ def _stringToNEVR(self, string):
+ i = string.rfind("-", 0, string.rfind("-")-1)
+ name = string[:i]
+ (epoch, ver, rel) = self._stringToVersion(string[i+1:])
+ return (name, epoch, ver, rel)
+
+ def _getLength(self, in_data):
+ length = 0
+ for val in in_data:
+ length = length * 256
+ length += ord(val)
+ return length
+
+ def _getDRPMInfo(self, filename):
+ d = deltarpm.readDeltaRPM(filename)
+ self.oldnevrstring = d['old_nevr']
+ self.oldnevr = self._stringToNEVR(d['old_nevr'])
+ self.sequence = d['seq']
+
+ def _stringToVersion(self, strng):
+ i = strng.find(':')
+ if i != -1:
+ epoch = strng[:i]
+ else:
+ epoch = '0'
+ j = strng.find('-')
+ if j != -1:
+ if strng[i + 1:j] == '':
+ version = None
+ else:
+ version = strng[i + 1:j]
+ release = strng[j + 1:]
+ else:
+ if strng[i + 1:] == '':
+ version = None
+ else:
+ version = strng[i + 1:]
+ release = None
+ return (epoch, version, release)
+
+ def xml_dump_metadata(self):
+ """takes an xml doc object and a package metadata entry node, populates a
+ package node with the md information"""
+
+ (oldname, oldepoch, oldver, oldrel) = self.oldnevr
+ sequence = "%s-%s" % (self.oldnevrstring, self.sequence)
+
+ delta_tag = """ <delta oldepoch="%s" oldversion="%s" oldrelease="%s">
+ <filename>%s</filename>
+ <sequence>%s</sequence>
+ <size>%s</size>
+ <checksum type="%s">%s</checksum>
+ </delta>\n""" % (oldepoch, oldver, oldrel, self.relativepath, sequence,
+ self.size, self.csum_type, self.csum)
+ return delta_tag
+
+def create_drpm(old_pkg, new_pkg, destdir):
+ """make a drpm file, if possible. returns None if nothing could
+ be created"""
+ drpmfn = '%s-%s-%s_%s-%s.%s.drpm' % (old_pkg.name, old_pkg.ver,
+ old_pkg.release, new_pkg.ver, new_pkg.release,
+ old_pkg.arch)
+ delta_rpm_path = os.path.join(destdir, drpmfn)
+ delta_command = '/usr/bin/makedeltarpm %s %s %s' % (old_pkg.localpath,
+ new_pkg.localpath,
+ delta_rpm_path)
+ if not os.path.exists(delta_rpm_path):
+ #TODO - check/verify the existing one a bit?
+ (code, out) = commands.getstatusoutput(delta_command)
+ if code:
+ print "Error genDeltaRPM for %s: exitcode was %s - Reported Error: %s" % (old_pkg.name, code, out)
+ return None
+
+ return delta_rpm_path
diff --git a/createrepo/merge.py b/createrepo/merge.py
new file mode 100644
index 0000000..b3b2ea1
--- /dev/null
+++ b/createrepo/merge.py
@@ -0,0 +1,139 @@
+#!/usr/bin/python -tt
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Copyright 2008 Red Hat, Inc - written by seth vidal skvidal at fedoraproject.org
+
+# merge repos from arbitrary repo urls
+
+import os
+import shutil
+import yum
+import yum.Errors
+from yum.misc import unique, getCacheDir
+import yum.update_md
+import rpmUtils.arch
+import operator
+import createrepo
+import tempfile
+
+# take repo paths from cli
+# produce new repo metadata from merging the two together.
+
+#TODO:
+# excludes?
+
+
+class RepoMergeBase:
+ def __init__(self, repolist=[], yumbase=None, mdconf=None, mdbase_class=None ):
+ self.repolist = repolist
+ self.outputdir = '%s/merged_repo' % os.getcwd()
+ self.exclude_tuples = []
+ self.sort_func = self._sort_func # callback function to magically sort pkgs
+ if not mdconf:
+ self.mdconf = createrepo.MetaDataConfig()
+ else:
+ self.mdconf = mdconf
+ if not mdbase_class:
+ self.mdbase_class = createrepo.MetaDataGenerator
+ else:
+ self.mdbase_class = mdbase_class
+ if not yumbase:
+ self.yumbase = yum.YumBase()
+ else:
+ self.yumbase = yumbase
+ self.yumbase.conf.cachedir = getCacheDir()
+ self.yumbase.conf.cache = 0
+ # default to all arches
+ self.archlist = unique(rpmUtils.arch.arches.keys() + rpmUtils.arch.arches.values())
+ self.groups = True
+ self.updateinfo = True
+
+ def _sort_func(self, repos):
+ """Default sort func for repomerge. Takes a list of repository objects
+ any package which is not to be included in the merged repo should be
+ delPackage()'d"""
+ # sort the repos by _merge_rank
+ # - lowest number is the highest rank (1st place, 2ndplace, etc)
+ repos.sort(key=operator.attrgetter('_merge_rank'))
+
+ for repo in repos:
+ for pkg in repo.sack:
+ others = self.yumbase.pkgSack.searchNevra(name=pkg.name, arch=pkg.arch)
+ # NOTE the above is definitely going to catch other versions which may
+ # be an invalid comparison
+ if len(others) > 1:
+ for thatpkg in others:
+ if pkg.repoid == thatpkg.repoid: continue
+ if pkg.repo._merge_rank < thatpkg.repo._merge_rank:
+ thatpkg.repo.sack.delPackage(thatpkg)
+
+ def merge_repos(self):
+ self.yumbase.repos.disableRepo('*')
+ # add our repos and give them a merge rank in the order they appear in
+ # in the repolist
+ count = 0
+ for r in self.repolist:
+ count +=1
+ rid = 'repo%s' % count
+ n = self.yumbase.add_enable_repo(rid, baseurls=[r],
+ metadata_expire=0,
+ timestamp_check=False)
+ n._merge_rank = count
+
+ #setup our sacks
+ self.yumbase._getSacks(archlist=self.archlist)
+
+ myrepos = self.yumbase.repos.listEnabled()
+
+ self.sort_func(myrepos)
+
+
+ def write_metadata(self, outputdir=None):
+ mytempdir = tempfile.mkdtemp()
+ if self.groups:
+ comps_fn = mytempdir + '/groups.xml'
+ compsfile = open(comps_fn, 'w')
+ compsfile.write(self.yumbase.comps.xml())
+ compsfile.close()
+ self.mdconf.groupfile=comps_fn
+
+ if self.updateinfo:
+ ui_fn = mytempdir + '/updateinfo.xml'
+ uifile = open(ui_fn, 'w')
+ umd = yum.update_md.UpdateMetadata()
+ for repo in self.yumbase.repos.listEnabled():
+ try: # attempt to grab the updateinfo.xml.gz from the repodata
+ umd.add(repo)
+ except yum.Errors.RepoMDError:
+ continue
+ umd.xml(fileobj=uifile)
+ uifile.close()
+ self.mdconf.additional_metadata['updateinfo'] = ui_fn
+
+
+ self.mdconf.pkglist = self.yumbase.pkgSack
+ self.mdconf.directory = self.outputdir
+ if outputdir:
+ self.mdconf.directory = outputdir
+ # clean out what was there
+ if os.path.exists(self.mdconf.directory + '/repodata'):
+ shutil.rmtree(self.mdconf.directory + '/repodata')
+
+ if not os.path.exists(self.mdconf.directory):
+ os.makedirs(self.mdconf.directory)
+
+ mdgen = self.mdbase_class(config_obj=self.mdconf)
+ mdgen.doPkgMetadata()
+ mdgen.doRepoMetadata()
+ mdgen.doFinalMove()
diff --git a/createrepo/readMetadata.py b/createrepo/readMetadata.py
new file mode 100644
index 0000000..27d3690
--- /dev/null
+++ b/createrepo/readMetadata.py
@@ -0,0 +1,217 @@
+#!/usr/bin/python -t
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Copyright 2006 Red Hat
+
+import os
+import libxml2
+import stat
+from utils import errorprint, _
+
+from yum import repoMDObject
+
+
+class MetadataIndex(object):
+
+ def __init__(self, outputdir, opts=None):
+ if opts is None:
+ opts = {}
+ self.opts = opts
+ self.outputdir = outputdir
+ repodatadir = self.outputdir + '/repodata'
+ myrepomdxml = repodatadir + '/repomd.xml'
+ if os.path.exists(myrepomdxml):
+ repomd = repoMDObject.RepoMD('garbageid', myrepomdxml)
+ b = repomd.getData('primary').location[1]
+ f = repomd.getData('filelists').location[1]
+ o = repomd.getData('other').location[1]
+ basefile = os.path.join(self.outputdir, b)
+ filelistfile = os.path.join(self.outputdir, f)
+ otherfile = os.path.join(self.outputdir, o)
+ else:
+ basefile = filelistfile = otherfile = ""
+
+ self.files = {'base' : basefile,
+ 'filelist' : filelistfile,
+ 'other' : otherfile}
+ self.scan()
+
+ def scan(self):
+ """Read in and index old repo data"""
+ self.basenodes = {}
+ self.filesnodes = {}
+ self.othernodes = {}
+ self.pkg_ids = {}
+ if self.opts.get('verbose'):
+ print _("Scanning old repo data")
+ for fn in self.files.values():
+ if not os.path.exists(fn):
+ #cannot scan
+ errorprint(_("Warning: Old repodata file missing: %s") % fn)
+ return
+ root = libxml2.parseFile(self.files['base']).getRootElement()
+ self._scanPackageNodes(root, self._handleBase)
+ if self.opts.get('verbose'):
+ print _("Indexed %i base nodes" % len(self.basenodes))
+ root = libxml2.parseFile(self.files['filelist']).getRootElement()
+ self._scanPackageNodes(root, self._handleFiles)
+ if self.opts.get('verbose'):
+ print _("Indexed %i filelist nodes" % len(self.filesnodes))
+ root = libxml2.parseFile(self.files['other']).getRootElement()
+ self._scanPackageNodes(root, self._handleOther)
+ if self.opts.get('verbose'):
+ print _("Indexed %i other nodes" % len(self.othernodes))
+ #reverse index pkg ids to track references
+ self.pkgrefs = {}
+ for relpath, pkgid in self.pkg_ids.iteritems():
+ self.pkgrefs.setdefault(pkgid,[]).append(relpath)
+
+ def _scanPackageNodes(self, root, handler):
+ node = root.children
+ while node is not None:
+ if node.type != "element":
+ node = node.next
+ continue
+ if node.name == "package":
+ handler(node)
+ node = node.next
+
+ def _handleBase(self, node):
+ top = node
+ node = node.children
+ pkgid = None
+ mtime = None
+ size = None
+ relpath = None
+ do_stat = self.opts.get('do_stat', True)
+ while node is not None:
+ if node.type != "element":
+ node = node.next
+ continue
+ if node.name == "checksum":
+ pkgid = node.content
+ elif node.name == "time":
+ mtime = int(node.prop('file'))
+ elif node.name == "size":
+ size = int(node.prop('package'))
+ elif node.name == "location":
+ relpath = node.prop('href')
+ node = node.next
+ if relpath is None:
+ print _("Incomplete data for node")
+ return
+ if pkgid is None:
+ print _("pkgid missing for %s") % relpath
+ return
+ if mtime is None:
+ print _("mtime missing for %s") % relpath
+ return
+ if size is None:
+ print _("size missing for %s") % relpath
+ return
+ if do_stat:
+ filepath = os.path.join(self.opts['pkgdir'], relpath)
+ try:
+ st = os.stat(filepath)
+ except OSError:
+ #file missing -- ignore
+ return
+ if not stat.S_ISREG(st.st_mode):
+ #ignore non files
+ return
+ #check size and mtime
+ if st.st_size != size:
+ if self.opts.get('verbose'):
+ print _("Size (%i -> %i) changed for file %s") % (size,st.st_size,filepath)
+ return
+ if int(st.st_mtime) != mtime:
+ if self.opts.get('verbose'):
+ print _("Modification time changed for %s") % filepath
+ return
+ #otherwise we index
+ self.basenodes[relpath] = top
+ self.pkg_ids[relpath] = pkgid
+
+ def _handleFiles(self, node):
+ pkgid = node.prop('pkgid')
+ if pkgid:
+ self.filesnodes[pkgid] = node
+
+ def _handleOther(self, node):
+ pkgid = node.prop('pkgid')
+ if pkgid:
+ self.othernodes[pkgid] = node
+
+ def getNodes(self, relpath):
+ """Return base, filelist, and other nodes for file, if they exist
+
+ Returns a tuple of nodes, or None if not found
+ """
+ bnode = self.basenodes.get(relpath,None)
+ if bnode is None:
+ return None
+ pkgid = self.pkg_ids.get(relpath,None)
+ if pkgid is None:
+ print _("No pkgid found for: %s") % relpath
+ return None
+ fnode = self.filesnodes.get(pkgid,None)
+ if fnode is None:
+ return None
+ onode = self.othernodes.get(pkgid,None)
+ if onode is None:
+ return None
+ return bnode, fnode, onode
+
+ def freeNodes(self,relpath):
+ #causing problems
+ """Free up nodes corresponding to file, if possible"""
+ bnode = self.basenodes.get(relpath,None)
+ if bnode is None:
+ print "Missing node for %s" % relpath
+ return
+ bnode.unlinkNode()
+ bnode.freeNode()
+ del self.basenodes[relpath]
+ pkgid = self.pkg_ids.get(relpath,None)
+ if pkgid is None:
+ print _("No pkgid found for: %s") % relpath
+ return None
+ del self.pkg_ids[relpath]
+ dups = self.pkgrefs.get(pkgid)
+ dups.remove(relpath)
+ if len(dups):
+ #still referenced
+ return
+ del self.pkgrefs[pkgid]
+ for nodes in self.filesnodes, self.othernodes:
+ node = nodes.get(pkgid)
+ if node is not None:
+ node.unlinkNode()
+ node.freeNode()
+ del nodes[pkgid]
+
+
+if __name__ == "__main__":
+ cwd = os.getcwd()
+ opts = {'verbose':1,
+ 'pkgdir': cwd}
+
+ idx = MetadataIndex(cwd, opts)
+ for fn in idx.basenodes.keys():
+ a,b,c, = idx.getNodes(fn)
+ a.serialize()
+ b.serialize()
+ c.serialize()
+ idx.freeNodes(fn)
diff --git a/createrepo/utils.py b/createrepo/utils.py
new file mode 100644
index 0000000..995c3b9
--- /dev/null
+++ b/createrepo/utils.py
@@ -0,0 +1,143 @@
+#!/usr/bin/python
+# util functions for createrepo
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+
+import os
+import os.path
+import sys
+import bz2
+import gzip
+from gzip import write32u, FNAME
+from yum import misc
+
+def errorprint(stuff):
+ print >> sys.stderr, stuff
+
+def _(args):
+ """Stub function for translation"""
+ return args
+
+
+class GzipFile(gzip.GzipFile):
+ def _write_gzip_header(self):
+ self.fileobj.write('\037\213') # magic header
+ self.fileobj.write('\010') # compression method
+ if hasattr(self, 'name'):
+ fname = self.name[:-3]
+ else:
+ fname = self.filename[:-3]
+ flags = 0
+ if fname:
+ flags = FNAME
+ self.fileobj.write(chr(flags))
+ write32u(self.fileobj, long(0))
+ self.fileobj.write('\002')
+ self.fileobj.write('\377')
+ if fname:
+ self.fileobj.write(fname + '\000')
+
+
+def _gzipOpen(filename, mode="rb", compresslevel=9):
+ return GzipFile(filename, mode, compresslevel)
+
+def bzipFile(source, dest):
+
+ s_fn = open(source, 'rb')
+ destination = bz2.BZ2File(dest, 'w', compresslevel=9)
+
+ while True:
+ data = s_fn.read(1024000)
+
+ if not data: break
+ destination.write(data)
+
+ destination.close()
+ s_fn.close()
+
+
+def returnFD(filename):
+ try:
+ fdno = os.open(filename, os.O_RDONLY)
+ except OSError:
+ raise MDError, "Error opening file"
+ return fdno
+
+def checkAndMakeDir(directory):
+ """
+ check out the directory and make it, if possible, return 1 if done, else return 0
+ """
+ if os.path.exists(directory):
+ if not os.path.isdir(directory):
+ #errorprint(_('%s is not a dir') % directory)
+ result = False
+ else:
+ if not os.access(directory, os.W_OK):
+ #errorprint(_('%s is not writable') % directory)
+ result = False
+ else:
+ result = True
+ else:
+ try:
+ os.mkdir(directory)
+ except OSError, e:
+ #errorprint(_('Error creating dir %s: %s') % (directory, e))
+ result = False
+ else:
+ result = True
+ return result
+
+def checksum_and_rename(fn_path, sumtype='sha256'):
+ """checksum the file rename the file to contain the checksum as a prefix
+ return the new filename"""
+ csum = misc.checksum(sumtype, fn_path)
+ fn = os.path.basename(fn_path)
+ fndir = os.path.dirname(fn_path)
+ csum_fn = csum + '-' + fn
+ csum_path = os.path.join(fndir, csum_fn)
+ os.rename(fn_path, csum_path)
+ return (csum, csum_path)
+
+
+
+def encodefilenamelist(filenamelist):
+ return '/'.join(filenamelist)
+
+def encodefiletypelist(filetypelist):
+ result = ''
+ ftl = {'file':'f', 'dir':'d', 'ghost':'g'}
+ for x in filetypelist:
+ result += ftl[x]
+ return result
+
+def split_list_into_equal_chunks(seq, num_chunks):
+ avg = len(seq) / float(num_chunks)
+ out = []
+ last = 0.0
+ while last < len(seq):
+ out.append(seq[int(last):int(last + avg)])
+ last += avg
+
+ return out
+
+
+class MDError(Exception):
+ def __init__(self, value=None):
+ Exception.__init__(self)
+ self.value = value
+
+ def __str__(self):
+ return self.value
diff --git a/createrepo/yumbased.py b/createrepo/yumbased.py
new file mode 100644
index 0000000..ac06196
--- /dev/null
+++ b/createrepo/yumbased.py
@@ -0,0 +1,235 @@
+#!/usr/bin/python -tt
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Copyright 2007 Red Hat, Inc - written by seth vidal skvidal at fedoraproject.org
+
+
+import os
+import rpm
+import types
+
+from yum.packages import YumLocalPackage
+from yum.Errors import *
+from yum import misc
+import utils
+import tempfile
+
+class CreateRepoPackage(YumLocalPackage):
+ def __init__(self, ts, package, sumtype=None, external_data={}):
+ YumLocalPackage.__init__(self, ts, package)
+ if sumtype:
+ self.checksum_type = sumtype
+
+ if external_data:
+ for (key, val) in external_data.items():
+ setattr(self, key, val)
+
+
+ def _do_checksum(self):
+ """return a checksum for a package:
+ - check if the checksum cache is enabled
+ if not - return the checksum
+ if so - check to see if it has a cache file
+ if so, open it and return the first line's contents
+ if not, grab the checksum and write it to a file for this pkg
+ """
+ # already got it
+ if self._checksum:
+ return self._checksum
+
+ # not using the cachedir
+ if not hasattr(self, '_cachedir') or not self._cachedir:
+ self._checksum = misc.checksum(self.checksum_type, self.localpath)
+ self._checksums = [(self.checksum_type, self._checksum, 1)]
+ return self._checksum
+
+
+ t = []
+ if type(self.hdr[rpm.RPMTAG_SIGGPG]) is not types.NoneType:
+ t.append("".join(self.hdr[rpm.RPMTAG_SIGGPG]))
+ if type(self.hdr[rpm.RPMTAG_SIGPGP]) is not types.NoneType:
+ t.append("".join(self.hdr[rpm.RPMTAG_SIGPGP]))
+ if type(self.hdr[rpm.RPMTAG_HDRID]) is not types.NoneType:
+ t.append("".join(self.hdr[rpm.RPMTAG_HDRID]))
+
+ kcsum = misc.Checksums(checksums=[self.checksum_type])
+ kcsum.update("".join(t))
+ key = kcsum.hexdigest()
+
+ csumtag = '%s-%s-%s-%s' % (os.path.basename(self.localpath),
+ key, self.size, self.filetime)
+ csumfile = '%s/%s' % (self._cachedir, csumtag)
+
+ if os.path.exists(csumfile) and float(self.filetime) <= float(os.stat(csumfile)[-2]):
+ csumo = open(csumfile, 'r')
+ checksum = csumo.readline()
+ csumo.close()
+
+ else:
+ checksum = misc.checksum(self.checksum_type, self.localpath)
+
+ # This is atomic cache creation via. rename, so we can have two
+ # tasks using the same cachedir ... mash does this.
+ try:
+ (csumo, tmpfilename) = tempfile.mkstemp(dir=self._cachedir)
+ csumo = os.fdopen(csumo, 'w', -1)
+ csumo.write(checksum)
+ csumo.close()
+ os.rename(tmpfilename, csumfile)
+ except:
+ pass
+
+ self._checksum = checksum
+ self._checksums = [(self.checksum_type, checksum, 1)]
+
+ return self._checksum
+
+ # sqlite-direct dump code below here :-/
+
+ def _sqlite_null(self, item):
+ if not item:
+ return None
+ return item
+
+ def do_primary_sqlite_dump(self, cur):
+ """insert primary data in place, this assumes the tables exist"""
+ if self.crp_reldir and self.localpath.startswith(self.crp_reldir):
+ relpath = self.localpath.replace(self.crp_reldir, '')
+ if relpath[0] == '/': relpath = relpath[1:]
+ else:
+ relpath = self.localpath
+
+ p = (self.crp_packagenumber, self.checksum, self.name, self.arch,
+ self.version, self.epoch, self.release, self.summary.strip(),
+ self.description.strip(), self._sqlite_null(self.url), self.filetime,
+ self.buildtime, self._sqlite_null(self.license),
+ self._sqlite_null(self.vendor), self._sqlite_null(self.group),
+ self._sqlite_null(self.buildhost), self._sqlite_null(self.sourcerpm),
+ self.hdrstart, self.hdrend, self._sqlite_null(self.packager),
+ self.packagesize, self.size, self.archivesize, relpath,
+ self.crp_baseurl, self.checksum_type)
+
+ q = """insert into packages values (?, ?, ?, ?, ?, ?,
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?,
+ ?, ?, ?)"""
+
+ # write out all of do_primary_sqlite as an executescript - work on the
+ # quoting for pretty much any contingency - take from sqlutils.py
+ #
+ # e
+ #p = None
+ #q = """insert into packages values (%s, %s, %s, %s, """
+
+ cur.execute(q, p)
+
+ # provides, obsoletes, conflicts
+ for pco in ('obsoletes', 'provides', 'conflicts'):
+ thispco = []
+ for (name, flag, (epoch, ver, rel)) in getattr(self, pco):
+ thispco.append((name, flag, epoch, ver, rel, self.crp_packagenumber))
+
+ q = "insert into %s values (?, ?, ?, ?, ?, ?)" % pco
+ cur.executemany(q, thispco)
+
+ # requires
+ reqs = []
+ for (name, flag, (epoch, ver, rel), pre) in self._requires_with_pre():
+ if name.startswith('rpmlib('):
+ continue
+ pre_bool = 'FALSE'
+ if pre == 1:
+ pre_bool = 'TRUE'
+ reqs.append((name, flag, epoch, ver,rel, self.crp_packagenumber, pre_bool))
+ q = "insert into requires values (?, ?, ?, ?, ?, ?, ?)"
+ cur.executemany(q, reqs)
+
+ # files
+ p = []
+ for f in self._return_primary_files():
+ p.append((f,))
+
+ if p:
+ q = "insert into files values (?, 'file', %s)" % self.crp_packagenumber
+ cur.executemany(q, p)
+
+ # dirs
+ p = []
+ for f in self._return_primary_dirs():
+ p.append((f,))
+ if p:
+ q = "insert into files values (?, 'dir', %s)" % self.crp_packagenumber
+ cur.executemany(q, p)
+
+
+ # ghosts
+ p = []
+ for f in self._return_primary_files(list_of_files = self.returnFileEntries('ghost')):
+ p.append((f,))
+ if p:
+ q = "insert into files values (?, 'ghost', %s)" % self.crp_packagenumber
+ cur.executemany(q, p)
+
+
+
+ def do_filelists_sqlite_dump(self, cur):
+ """inserts filelists data in place, this assumes the tables exist"""
+ # insert packagenumber + checksum into 'packages' table
+ q = 'insert into packages values (?, ?)'
+ p = (self.crp_packagenumber, self.checksum)
+
+ cur.execute(q, p)
+
+ # break up filelists and encode them
+ dirs = {}
+ for (filetype, files) in [('file', self.filelist), ('dir', self.dirlist),
+ ('ghost', self.ghostlist)]:
+ for filename in files:
+ (dirname,filename) = (os.path.split(filename))
+ if not dirs.has_key(dirname):
+ dirs[dirname] = {'files':[], 'types':[]}
+ dirs[dirname]['files'].append(filename)
+ dirs[dirname]['types'].append(filetype)
+
+ # insert packagenumber|dir|files|types into files table
+ p = []
+ for (dirname,direc) in dirs.items():
+ p.append((self.crp_packagenumber, dirname,
+ utils.encodefilenamelist(direc['files']),
+ utils.encodefiletypelist(direc['types'])))
+ if p:
+ q = 'insert into filelist values (?, ?, ?, ?)'
+ cur.executemany(q, p)
+
+
+ def do_other_sqlite_dump(self, cur):
+ """inserts changelog data in place, this assumes the tables exist"""
+ # insert packagenumber + checksum into 'packages' table
+ q = 'insert into packages values (?, ?)'
+ p = (self.crp_packagenumber, self.checksum)
+
+ cur.execute(q, p)
+
+ if self.changelog:
+ q = 'insert into changelog ("pkgKey", "date", "author", "changelog") values (%s, ?, ?, ?)' % self.crp_packagenumber
+ cur.executemany(q, self.changelog)
+
+
+ def do_sqlite_dump(self, md_sqlite):
+ """write the metadata out to the sqlite dbs"""
+ self.do_primary_sqlite_dump(md_sqlite.primary_cursor)
+ md_sqlite.pri_cx.commit()
+ self.do_filelists_sqlite_dump(md_sqlite.filelists_cursor)
+ md_sqlite.file_cx.commit()
+ self.do_other_sqlite_dump(md_sqlite.other_cursor)
+ md_sqlite.other_cx.commit()
diff --git a/dmd.py b/dmd.py
new file mode 100755
index 0000000..684bac6
--- /dev/null
+++ b/dmd.py
@@ -0,0 +1,156 @@
+#!/usr/bin/python
+
+# dmd - Generate and apply deltas between repository metadata
+#
+# Copyright (C) 2007 James Bowes <jbowes@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+import sys
+from lxml.etree import parse, tostring, Element
+
+
+class MdType(object):
+ def __init__(self, namespace, rootelem):
+ self.ns = "http://linux.duke.edu/metadata/%s" % namespace
+ self.sns = "{%s}" % self.ns
+ self.deltasns = "{http://linux.duke.edu/metadata/delta}"
+ self.root = rootelem
+
+ def get_pkg_id(self, pkg):
+ return pkg.findtext(self.sns + "checksum")
+
+ def make_hash(self, tree):
+ pkgshash = {}
+ for pkg in tree:
+ pkgid = self.get_pkg_id(pkg)
+ pkgshash[pkgid] = pkg
+
+ return pkgshash
+
+ def make_pkg_elem(self, pkgid, pkg):
+ pkgelem = Element("package")
+ pkgelem.set('name', pkg.findtext(self.sns + 'name'))
+ pkgelem.set('arch', pkg.findtext(self.sns + 'arch'))
+ pkgelem.set('pkgid', pkgid)
+ verelem = pkg.find(self.sns + 'version')
+ verelem.tag = "version"
+ pkgelem.append(verelem)
+
+ return pkgelem
+
+ def diff_trees(self, oldtree, newtree):
+ oldpkgs = oldtree.getroot().getchildren()
+ newpkgs = newtree.getroot().getchildren()
+
+ oldpkgshash = self.make_hash(oldpkgs)
+ newpkgshash = self.make_hash(newpkgs)
+
+ diff = Element(self.root,
+ nsmap = {None : self.ns,
+ "rpm" : "http://linux.duke.edu/metadata/rpm",
+ "delta" : "http://linux.duke.edu/metadata/delta"})
+ additions = Element("delta:additions")
+ diff.append(additions)
+ removals = Element("delta:removals")
+
+ diff.append(removals)
+
+ for pkgid, pkg in newpkgshash.iteritems():
+ if not oldpkgshash.has_key(pkgid):
+ additions.append(pkg)
+
+ for pkgid, pkg in oldpkgshash.iteritems():
+ if not newpkgshash.has_key(pkgid):
+ pkgelem = self.make_pkg_elem(pkgid, pkg)
+ removals.append(pkgelem)
+
+ diff.set("packages", str(len(removals) + len(additions)))
+
+ print tostring(diff, pretty_print=True)
+
+ def patch_tree(self, oldtree, deltatree):
+ oldroot = oldtree.getroot()
+ oldpkgs = oldroot.getchildren()
+
+ oldpkgshash = self.make_hash(oldpkgs)
+
+ additions = deltatree.find(self.deltasns + 'additions').getchildren()
+ removals = deltatree.find(self.deltasns + 'removals').getchildren()
+
+ for pkg in additions:
+ pkgid = self.get_pkg_id(pkg)
+ if oldpkgshash.has_key(pkgid):
+ print >> sys.stderr, "Package %s already exists" % pkgid
+ sys.exit(1)
+ oldroot.append(pkg)
+
+ for pkg in removals:
+ pkgid = pkg.get('pkgid')
+ if not oldpkgshash.has_key(pkgid):
+ print >> sys.stderr, "Package %s does not exist" % pkgid
+ sys.exit(1)
+ oldroot.remove(oldpkgshash[pkgid])
+
+ oldcount = int(oldroot.get('packages'))
+ newcount = oldcount + len(additions) - len(removals)
+ oldroot.set('packages', str(newcount))
+ print tostring(oldtree, pretty_print=True)
+
+
+class OtherMdType(MdType):
+ def get_pkg_id(self, pkg):
+ return pkg.get('pkgid')
+
+ def make_pkg_elem(self, pkgid, pkg):
+ pkgelem = Element("package")
+ pkgelem.set('name', pkg.get('name'))
+ pkgelem.set('arch', pkg.get('arch'))
+ pkgelem.set('pkgid', pkgid)
+ verelem = pkg.find(self.sns + 'version')
+ verelem.tag = "version"
+
+ return pkgelem
+
+
+mdtypeinfo = {
+ 'primary' : MdType('common', 'metadata'),
+ 'filelists' : OtherMdType('filelists', 'filelists'),
+ 'other' : OtherMdType('other', 'other'),
+ }
+
+
+def usage(progname):
+ print "usage: %s [diff|patch] MDTYPE FILE1 FILE2" % progname
+ sys.exit()
+
+def main(args):
+ if len(args) != 5:
+ usage(args[0])
+ if args[1] not in ('diff', 'patch'):
+ usage(args[0])
+ if args[2] not in ('primary', 'filelists', 'other'):
+ usage(args[0])
+
+ oldtree = parse(args[3])
+ newtree = parse(args[4])
+
+ if args[1] == 'diff':
+ mdtypeinfo[args[2]].diff_trees(oldtree, newtree)
+ else:
+ mdtypeinfo[args[2]].patch_tree(oldtree, newtree)
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..2e70622
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,99 @@
+SHELL = /bin/sh
+top_srcdir = ..
+srcdir = ../docs
+prefix = /usr
+exec_prefix = ${prefix}
+
+bindir = ${exec_prefix}/bin
+sbindir = ${exec_prefix}/sbin
+libexecdir = ${exec_prefix}/libexec
+datadir = ${prefix}/share
+sysconfdir = ${prefix}/etc
+sharedstatedir = ${prefix}/com
+localstatedir = ${prefix}/var
+libdir = ${exec_prefix}/lib
+infodir = ${prefix}/info
+docdir =
+includedir = ${prefix}/include
+oldincludedir = /usr/include
+mandir = ${datadir}/man
+
+pkgdatadir = $(datadir)/$(PKGNAME)
+pkglibdir = $(libdir)/$(PKGNAME)
+pkgincludedir = $(includedir)/$(PKGNAME)
+top_builddir = ../
+
+# all dirs
+DIRS = $(DESTDIR)$(bindir) $(DESTDIR)/etc $(DESTDIR)$(pkgdatadir) $(DESTDIR)$(mandir)
+
+
+# INSTALL scripts
+INSTALL = install -p --verbose
+INSTALL_BIN = $(INSTALL) -m 755
+INSTALL_DIR = $(INSTALL) -m 755 -d
+INSTALL_DATA = $(INSTALL) -m 644
+INSTALL_MODULES = $(INSTALL) -m 755 -D
+RM = rm -f
+
+
+all:
+ echo "nothing to do"
+
+install: all installdirs
+ mkdir -p $(DESTDIR)$(mandir)/man8
+ mkdir -p $(DESTDIR)$(mandir)/man1
+ $(INSTALL_DATA) createrepo.8 $(DESTDIR)$(mandir)/man8/createrepo.8
+ $(INSTALL_DATA) modifyrepo.1 $(DESTDIR)$(mandir)/man1/modifyrepo.1
+ $(INSTALL_DATA) mergerepo.1 $(DESTDIR)$(mandir)/man1/mergerepo.1
+
+
+uninstall:
+ $(RM) $(bindir)/$(PKGNAME)
+
+
+
+clean:
+
+
+distclean:
+ $(RM) -rf .libs
+ $(RM) -f core
+ $(RM) -f *~
+
+
+mostlyclean:
+ $(MAKE) clean
+
+
+maintainer-clean:
+ $(MAKE) distclean
+
+
+distfiles:
+ distdir=$(PKGNAME)-$(VERSION); \
+ mkdir $(top_srcdir)/.disttmp/$$distdir/docs;\
+ cp \
+ $(srcdir)/createrepo.8 \
+ $(srcdir)/modifyrepo.1 \
+ $(srcdir)/mergerepo.1 \
+ $(srcdir)/Makefile \
+ $(top_srcdir)/.disttmp/$$distdir/docs
+
+dailyfiles:
+ distdir=$(PKGNAME); \
+ mkdir $(top_srcdir)/.disttmp/$$distdir/docs;\
+ cp \
+ $(srcdir)/createrepo.8 \
+ $(srcdir)/modifyrepo.1 \
+ $(srcdir)/mergerepo.1 \
+ $(srcdir)/Makefile \
+ $(top_srcdir)/.disttmp/$$distdir/docs
+
+installdirs:
+ $(MAKE) -C $(top_srcdir) installdirs
+
+
+.PHONY: all install install-strip uninstall clean distclean mostlyclean maintainer-clean info dvi dist distfiles check installcheck installdirs dailyfiles
+
+
+
diff --git a/docs/createrepo.8 b/docs/createrepo.8
new file mode 100644
index 0000000..e3c4c3b
--- /dev/null
+++ b/docs/createrepo.8
@@ -0,0 +1,139 @@
+.TH "createrepo" "8" "2005 Jan 2" "Seth Vidal" ""
+
+.SH "NAME"
+createrepo \- Create repomd (xml-rpm-metadata) repository
+
+.SH "SYNOPSIS"
+\fBcreaterepo\fP [options] <directory>
+.PP
+
+.SH "DESCRIPTION"
+\fBcreaterepo\fP is a program that creates a repomd (xml-based rpm metadata) repository from a set of rpms.
+
+.SH "OPTIONS"
+.IP "\fB\-u --baseurl\fP <url>"
+Optional base URL location for all files.
+.IP "\fB\-o --outputdir\fP <url>"
+Optional output directory (useful for read only media).
+.IP "\fB\-x --excludes\fP <package>"
+File globs to exclude, can be specified multiple times.
+.IP "\fB\-i --pkglist\fP <filename>"
+specify a text file which contains the complete list of files to
+include in the repository from the set found in the directory. File format is one
+package per line, no wildcards or globs.
+.IP "\fB\-n --includepkg\fP"
+specify pkgs to include on the command line. Takes urls as well as local paths.
+.IP "\fB\-q --quiet\fP"
+Run quietly.
+.IP "\fB\-g --groupfile\fP <groupfile>"
+A precreated xml filename to point to for group information.
+.br
+See examples section below for further explanation.
+.IP "\fB\-v --verbose\fP"
+Run verbosely.
+.IP "\fB\-c --cachedir\fP <path>"
+Specify a directory to use as a cachedir. This allows createrepo to create a
+cache of checksums of packages in the repository. In consecutive runs of
+createrepo over the same repository of files that do not have a complete
+change out of all packages this decreases the processing time dramatically.
+.br
+.IP "\fB\--update\fP"
+If metadata already exists in the outputdir and an rpm is unchanged
+(based on file size and mtime) since the metadata was generated, reuse
+the existing metadata rather than recalculating it. In the case of a
+large repository with only a few new or modified rpms this can
+significantly reduce I/O and processing time.
+.br
+.IP "\fB\--skip-stat\fP"
+skip the stat() call on a --update, assumes if the filename is the same
+then the file is still the same (only use this if you're fairly trusting or
+gullible).
+.br
+.IP "\fB\-C --checkts\fP"
+Don't generate repo metadata, if their timestamps are newer than its rpms.
+This option decreases the processing time drastically again, if you happen
+to run it on an unmodified repo, but it is (currently) mutual exclusive
+with the --split option.
+.br
+.IP "\fB\--split\fP"
+Run in split media mode. Rather than pass a single directory, take a set of
+directories corresponding to different volumes in a media set.
+.br
+.IP "\fB\-p --pretty\fP"
+Output xml files in pretty format.
+.IP "\fB\-V --version\fP"
+Output version.
+.IP "\fB\-h --help\fP"
+Show help menu.
+
+.IP "\fB\-d --database\fP"
+Generate sqlite databases for use with yum. This is now the default.
+
+.IP "\fB\--no-database\fP"
+Do not generate sqlite databases in the repository.
+
+.IP "\fB\-S --skip-symlinks\fP"
+Ignore symlinks of packages
+.IP "\fB\-s --checksum\fP"
+Choose the checksum type used in repomd.xml and for packages in the metadata.
+The default is now "sha256" (if python has hashlib). The older default was
+"sha", which is actually "sha1", however explicitly using "sha1" doesn't work
+on older (3.0.x) versions of yum, you need to specify "sha".
+.IP "\fB\--profile\fP"
+Output time based profiling information.
+.IP "\fB\--changelog-limit\fP CHANGELOG_LIMIT"
+Only import the last N changelog entries, from each rpm, into the metadata
+.IP "\fB\--unique-md-filenames\fP"
+Include the file's checksum in the metadata filename, helps HTTP caching (default)
+
+.IP "\fB\--simple-md-filenames\fP"
+Do not include the file's checksum in the metadata filename.
+
+.IP "\fB\--distro\fP"
+Specify distro tags. Can be specified more than once. Optional syntax specifying a
+cpeid(http://cpe.mitre.org/) --distro=cpeid,distrotag
+.IP "\fB\--content\fP"
+Specify keyword/tags about the content of the repository. Can be specified more than once.
+.IP "\fB\--repo\fP"
+Specify keyword/tags about the repository itself. Can be specified more than once.
+.IP "\fB\--revision\fP"
+Arbitrary string for a repository revision.
+.IP "\fB\--deltas\fP"
+Tells createrepo to generate deltarpms and the delta metadata
+.IP "\fB\--oldpackagedirs\fP PATH"
+paths to look for older pkgs to delta against. Can be specified multiple times
+.IP "\fB\--num-deltas\fP int"
+the number of older versions to make deltas against. Defaults to 1
+
+
+.SH "EXAMPLES"
+Here is an example of a repository with a groups file. Note that the
+groups file should be in the same directory as the rpm packages
+(i.e. /path/to/rpms/comps.xml).
+.br
+.PP
+\fBcreaterepo\fP \-g comps.xml /path/to/rpms
+
+.SH "FILES"
+.nf
+repodata/filelists.xml.gz
+repodata/other.xml.gz
+repodata/primary.xml.gz
+repodata/repomd.xml
+.fi
+.PP
+.SH "SEE ALSO"
+.I yum (8) yum.conf (5)
+
+.PP
+.SH "AUTHORS"
+.nf
+See the Authors file
+.fi
+
+.PP
+.SH "BUGS"
+Any bugs which are found should be emailed to the mailing list:
+rpm-metadata@lists.baseurl.org
+or reported in trac at: http://createrepo.baseurl.org
+.fi
diff --git a/docs/mergerepo.1 b/docs/mergerepo.1
new file mode 100644
index 0000000..2529e7a
--- /dev/null
+++ b/docs/mergerepo.1
@@ -0,0 +1,56 @@
+.TH "mergerepo" "1" "2008 Oct 21" "Seth Vidal" ""
+
+.SH "NAME"
+mergerepo \- Merge multiple repositories together
+
+.SH "SYNOPSIS"
+\fBmergerepo\fP --repo repo1 --repo repo2
+.PP
+
+.SH "DESCRIPTION"
+\fBmergerepo\fP is a program that allows you merge multiple repositories
+into a single repository while referring to the remote location for all
+packages.
+
+.SH "OPTIONS"
+.IP "\fB\-r --repo\fP <url>"
+Url to a repository to be merged.
+
+.IP "\fB\-o --outputdir <directory>\fP"
+Path where merged repository metadata should be written to. If not specified
+repository metadata will be written to `pwd`/merged_repo/.
+
+.IP "\fB\-d --database\fP"
+Generate sqlite databases of the merged repository metadata.
+
+.IP "\fB\-a --archlist\fP"
+Specify a comma-separated list of architectures to use. Defaults to ALL.
+
+.IP "\fB\--nogroups\fP"
+Do not merge/include groups metadata in the repository.
+
+.IP "\fB\--noupdateinfo\fP"
+Do not merge/include updateinfo metadata in the repository.
+
+
+.SH "EXAMPLES"
+.PP
+$ \fBmergerepo\fP --repo=http://myurl.org/repo1 --repo=http://myurl.org/repo2 -d -o /tmp/mymergedrepo
+
+.PP
+.SH "SEE ALSO"
+.I createrepo (8)
+
+.PP
+.SH "AUTHORS"
+.nf
+Seth Vidal <skvidal@fedoraproject.org>
+.fi
+
+.PP
+.SH "BUGS"
+Any bugs which are found should be emailed to the mailing list:
+rpm-metadata@lists.baseurl.org or filed as tickets at:
+http://createrepo.baseurl.org/
+
+.fi
diff --git a/docs/modifyrepo.1 b/docs/modifyrepo.1
new file mode 100644
index 0000000..cc031f5
--- /dev/null
+++ b/docs/modifyrepo.1
@@ -0,0 +1,41 @@
+.TH "modifyrepo" "1" "2007 Dec 3" "Luke Macken" ""
+
+.SH "NAME"
+modifyrepo \- Modify a repomd (xml-rpm-metadata) repository
+
+.SH "SYNOPSIS"
+\fBmodifyrepo\fP [options] <input metadata> <output repodata>
+.PP
+
+.SH "DESCRIPTION"
+\fBmodifyrepo\fP is a program that allows you to insert arbitrary metadata into a repomd (xml-based rpm metadata) repository.
+
+.SH "EXAMPLES"
+.PP
+$ \fBmodifyrepo\fP --mdtype=newmd metadata.xml /repository/repodata
+.br
+Wrote: /repository/repodata/metadata.xml.gz
+ type = newmd
+ location = repodata/metadata.xml.gz
+ checksum = 1d7ee93db2964e7f85e07ec19b3204591da1050c
+ timestamp = 1196716296.0
+ open-checksum = 824d936dc7dfff029379797b311af0cc66af4115
+.br
+Wrote: /repository/repodata/repomd.xml
+
+.PP
+.SH "SEE ALSO"
+.I createrepo (8)
+
+.PP
+.SH "AUTHORS"
+.nf
+Luke Macken <lmacken@redhat.com>
+Seth Vidal <skvidal@fedoraproject.org>
+.fi
+
+.PP
+.SH "BUGS"
+Any bugs which are found should be emailed to the mailing list:
+rpm-metadata@lists.baseurl.org
+.fi
diff --git a/genpkgmetadata.py b/genpkgmetadata.py
new file mode 100755
index 0000000..8c98191
--- /dev/null
+++ b/genpkgmetadata.py
@@ -0,0 +1,277 @@
+#!/usr/bin/python -t
+# primary functions and glue for generating the repository metadata
+#
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Copyright 2004 Duke University
+# Portions Copyright 2009 Red Hat, Inc -
+# written by seth vidal skvidal at fedoraproject.org
+
+import os
+import sys
+import re
+from optparse import OptionParser
+import time
+
+import createrepo
+from createrepo import MDError
+from createrepo.utils import errorprint, _
+import yum.misc
+
+
+def parse_args(args, conf):
+ """
+ Parse the command line args. return a config object.
+ Sanity check all the things being passed in.
+ """
+
+ _def = yum.misc._default_checksums[0]
+ _avail = yum.misc._available_checksums
+ parser = OptionParser(version = "createrepo %s" % createrepo.__version__)
+ # query options
+ parser.add_option("-q", "--quiet", default=False, action="store_true",
+ help="output nothing except for serious errors")
+ parser.add_option("-v", "--verbose", default=False, action="store_true",
+ help="output more debugging info.")
+ parser.add_option("--profile", default=False, action="store_true",
+ help="output timing/profile info.")
+ parser.add_option("-x", "--excludes", default=[], action="append",
+ help="files to exclude")
+ parser.add_option("--basedir", default=os.getcwd(),
+ help="basedir for path to directories")
+ parser.add_option("-u", "--baseurl", default=None,
+ help="baseurl to append on all files")
+ parser.add_option("-g", "--groupfile", default=None,
+ help="path to groupfile to include in metadata")
+ parser.add_option("-s", "--checksum", default=_def, dest='sumtype',
+ help="specify the checksum type to use (default: %s)" % _def)
+ parser.add_option("-p", "--pretty", default=False, action="store_true",
+ help="make sure all xml generated is formatted")
+ parser.add_option("-c", "--cachedir", default=None,
+ help="set path to cache dir")
+ parser.add_option("-C", "--checkts", default=False, action="store_true",
+ help="check timestamps on files vs the metadata to see " \
+ "if we need to update")
+ parser.add_option("-d", "--database", default=True, action="store_true",
+ help="create sqlite database files: now default, see --no-database to disable")
+ parser.add_option("--no-database", default=False, dest="nodatabase", action="store_true",
+ help="do not create sqlite dbs of metadata")
+ # temporarily disabled
+ #parser.add_option("--database-only", default=False, action="store_true",
+ # dest='database_only',
+ # help="Only make the sqlite databases - does not work with --update, yet")
+ parser.add_option("--update", default=False, action="store_true",
+ help="use the existing repodata to speed up creation of new")
+ parser.add_option("--update-md-path", default=None, dest='update_md_path',
+ help="use the existing repodata for --update from this path")
+ parser.add_option("--skip-stat", dest='skip_stat', default=False,
+ help="skip the stat() call on a --update, assumes if the file" \
+ "name is the same then the file is still the same " \
+ "(only use this if you're fairly trusting or gullible)",
+ action="store_true")
+ parser.add_option("--split", default=False, action="store_true",
+ help="generate split media")
+ parser.add_option("-i", "--pkglist", default=None,
+ help="use only the files listed in this file from the " \
+ "directory specified")
+ parser.add_option("-n", "--includepkg", default=[], action="append",
+ help="add this pkg to the list - can be specified multiple times")
+ parser.add_option("-o", "--outputdir", default=None,
+ help="<dir> = optional directory to output to")
+ parser.add_option("-S", "--skip-symlinks", dest="skip_symlinks",
+ default=False, action="store_true", help="ignore symlinks of packages")
+ parser.add_option("--changelog-limit", dest="changelog_limit",
+ default=None, help="only import the last N changelog entries")
+ parser.add_option("--unique-md-filenames", dest="unique_md_filenames",
+ help="include the file's checksum in the filename, helps with proxies",
+ default=True, action="store_true")
+ parser.add_option("--simple-md-filenames", dest="simple_md_filenames",
+ help="do not include the file's checksum in the filename, helps with proxies",
+ default=False, action="store_true")
+ parser.add_option("--distro", default=[], action="append",
+ help="distro tag and optional cpeid: --distro" "'cpeid,textname'")
+ parser.add_option("--content", default=[], dest='content_tags',
+ action="append", help="tags for the content in the repository")
+ parser.add_option("--repo", default=[], dest='repo_tags',
+ action="append", help="tags to describe the repository itself")
+ parser.add_option("--revision", default=None,
+ help="user-specified revision for this repository")
+ parser.add_option("--deltas", default=False, action="store_true",
+ help="create delta rpms and metadata")
+ parser.add_option("--oldpackagedirs", default=[], dest="oldpackage_paths",
+ action="append", help="paths to look for older pkgs to delta against")
+ parser.add_option("--num-deltas", default=1, dest='num_deltas', type='int',
+ help="the number of older versions to make deltas against")
+ parser.add_option("--read-pkgs-list", default=None, dest='read_pkgs_list',
+ help="output the paths to the pkgs actually read useful with --update")
+ parser.add_option("--max-delta-rpm-size", default=100000000,
+ dest='max_delta_rpm_size', type='int',
+ help="max size of an rpm that to run deltarpm against (in bytes)")
+
+ parser.add_option("--workers", default=1,
+ dest='workers', type='int',
+ help="number of workers to spawn to read rpms")
+
+ (opts, argsleft) = parser.parse_args(args)
+ if len(argsleft) > 1 and not opts.split:
+ errorprint(_('Error: Only one directory allowed per run.'))
+ parser.print_usage()
+ sys.exit(1)
+
+ elif len(argsleft) == 0:
+ errorprint(_('Error: Must specify a directory to index.'))
+ parser.print_usage()
+ sys.exit(1)
+
+ else:
+ directories = argsleft
+
+ if opts.sumtype == 'sha1':
+ errorprint(_('Warning: It is more compatible to use sha instead of sha1'))
+
+ if opts.sumtype != 'sha' and opts.sumtype not in _avail:
+ errorprint(_('Error: Checksum %s not available (sha, %s)') %
+ (opts.sumtype, ", ".join(sorted(_avail))))
+ sys.exit(1)
+
+ if opts.split and opts.checkts:
+ errorprint(_('--split and --checkts options are mutually exclusive'))
+ sys.exit(1)
+
+ if opts.simple_md_filenames:
+ opts.unique_md_filenames = False
+
+ if opts.nodatabase:
+ opts.database = False
+
+ # let's switch over to using the conf object - put all the opts into it
+ for opt in parser.option_list:
+ if opt.dest is None: # this is fairly silly
+ continue
+ # if it's not set, take the default from the base class
+ if getattr(opts, opt.dest) is None:
+ continue
+ setattr(conf, opt.dest, getattr(opts, opt.dest))
+
+ directory = directories[0]
+ conf.directory = directory
+ conf.directories = directories
+
+ # distro tag parsing
+
+ for spec in opts.distro:
+ if spec.find(',') == -1:
+ conf.distro_tags.append((None, spec))
+ else:
+ splitspec = spec.split(',')
+ conf.distro_tags.append((splitspec[0], splitspec[1]))
+
+ lst = []
+ if conf.pkglist:
+ pfo = open(conf.pkglist, 'r')
+ for line in pfo.readlines():
+ line = line.strip()
+ if re.match('^\s*\#.*', line) or re.match('^\s*$', line):
+ continue
+ lst.append(line)
+ pfo.close()
+
+ conf.pkglist = lst
+
+ if conf.includepkg:
+ conf.pkglist.extend(conf.includepkg)
+
+ if conf.changelog_limit: # make sure it is an int, not a string
+ conf.changelog_limit = int(conf.changelog_limit)
+
+ return conf
+
+class MDCallBack(object):
+ """cli callback object for createrepo"""
+ def __init__(self):
+ self.__show_progress = os.isatty(1)
+
+ def errorlog(self, thing):
+ """error log output"""
+ print >> sys.stderr, thing
+
+ def log(self, thing):
+ """log output"""
+ print thing
+
+ def progress(self, item, current, total):
+ """progress bar"""
+
+ if not self.__show_progress:
+ return
+ beg = "%*d/%d - " % (len(str(total)), current, total)
+ left = 80 - len(beg)
+ sys.stdout.write("\r%s%-*.*s" % (beg, left, left, item))
+ sys.stdout.flush()
+
+def main(args):
+ """createrepo from cli main flow"""
+ start_st = time.time()
+ conf = createrepo.MetaDataConfig()
+ conf = parse_args(args, conf)
+ if conf.profile:
+ print ('start time: %0.3f' % (time.time() - start_st))
+
+ mid_st = time.time()
+ try:
+ if conf.split:
+ mdgen = createrepo.SplitMetaDataGenerator(config_obj=conf,
+ callback=MDCallBack())
+ else:
+ mdgen = createrepo.MetaDataGenerator(config_obj=conf,
+ callback=MDCallBack())
+ if mdgen.checkTimeStamps():
+ if mdgen.conf.verbose:
+ print _('repo is up to date')
+ sys.exit(0)
+
+ if conf.profile:
+ print ('mid time: %0.3f' % (time.time() - mid_st))
+
+ pm_st = time.time()
+ mdgen.doPkgMetadata()
+ if conf.profile:
+ print ('pm time: %0.3f' % (time.time() - pm_st))
+ rm_st = time.time()
+ mdgen.doRepoMetadata()
+ if conf.profile:
+ print ('rm time: %0.3f' % (time.time() - rm_st))
+ fm_st = time.time()
+ mdgen.doFinalMove()
+ if conf.profile:
+ print ('fm time: %0.3f' % (time.time() - fm_st))
+
+
+ except MDError, errormsg:
+ errorprint(_('%s') % errormsg)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1:
+ if sys.argv[1] == 'profile':
+ import hotshot
+ p = hotshot.Profile(os.path.expanduser("~/createrepo.prof"))
+ p.run('main(sys.argv[2:])')
+ p.close()
+ else:
+ main(sys.argv[1:])
+ else:
+ main(sys.argv[1:])
diff --git a/mergerepo.py b/mergerepo.py
new file mode 100755
index 0000000..05e5f5e
--- /dev/null
+++ b/mergerepo.py
@@ -0,0 +1,85 @@
+#!/usr/bin/python -tt
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Copyright 2008 Red Hat, Inc - written by skvidal at fedoraproject.org
+
+# merge repos from arbitrary repo urls
+
+import sys
+import createrepo.merge
+from optparse import OptionParser
+
+#TODO:
+# excludes?
+# handle content/distro tags
+# support revision?
+
+
+def parse_args(args):
+ """Parse our opts/args"""
+ usage = """
+ mergerepo: take 2 or more repositories and merge their metadata into a new repo
+
+ mergerepo --repo=url --repo=url --outputdir=/some/path"""
+
+ parser = OptionParser(version = "mergerepo 0.1", usage=usage)
+ # query options
+ parser.add_option("-r", "--repo", dest='repos', default=[], action="append",
+ help="repo url")
+ parser.add_option("-a", "--archlist", default=[], action="append",
+ help="Defaults to all arches - otherwise specify arches")
+ parser.add_option("-d", "--database", default=True, action="store_true")
+ parser.add_option( "--no-database", default=False, action="store_true", dest="nodatabase")
+ parser.add_option("-o", "--outputdir", default=None,
+ help="Location to create the repository")
+ parser.add_option("", "--nogroups", default=False, action="store_true",
+ help="Do not merge group(comps) metadata")
+ parser.add_option("", "--noupdateinfo", default=False, action="store_true",
+ help="Do not merge updateinfo metadata")
+ (opts, argsleft) = parser.parse_args(args)
+
+ if len(opts.repos) < 2:
+ parser.print_usage()
+ sys.exit(1)
+
+ # sort out the comma-separated crap we somehow inherited.
+ archlist = []
+ for archs in opts.archlist:
+ for arch in archs.split(','):
+ archlist.append(arch)
+
+ opts.archlist = archlist
+
+ return opts
+
+def main(args):
+ """main"""
+ opts = parse_args(args)
+ rmbase = createrepo.merge.RepoMergeBase(opts.repos)
+ if opts.archlist:
+ rmbase.archlist = opts.archlist
+ if opts.outputdir:
+ rmbase.outputdir = opts.outputdir
+ if opts.nodatabase:
+ rmbase.mdconf.database = False
+ if opts.nogroups:
+ rmbase.groups = False
+ if opts.noupdateinfo:
+ rmbase.updateinfo = False
+
+ rmbase.merge_repos()
+ rmbase.write_metadata()
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/modifyrepo.py b/modifyrepo.py
new file mode 100755
index 0000000..17094a4
--- /dev/null
+++ b/modifyrepo.py
@@ -0,0 +1,148 @@
+#!/usr/bin/python
+# This tools is used to insert arbitrary metadata into an RPM repository.
+# Example:
+# ./modifyrepo.py updateinfo.xml myrepo/repodata
+# or in Python:
+# >>> from modifyrepo import RepoMetadata
+# >>> repomd = RepoMetadata('myrepo/repodata')
+# >>> repomd.add('updateinfo.xml')
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# (C) Copyright 2006 Red Hat, Inc.
+# Luke Macken <lmacken@redhat.com>
+# modified by Seth Vidal 2008
+
+import os
+import sys
+from createrepo import __version__
+from createrepo.utils import checksum_and_rename, GzipFile, MDError
+from yum.misc import checksum
+
+from yum.repoMDObject import RepoMD, RepoMDError, RepoData
+from xml.dom import minidom
+from optparse import OptionParser
+
+
+class RepoMetadata:
+
+ def __init__(self, repo):
+ """ Parses the repomd.xml file existing in the given repo directory. """
+ self.repodir = os.path.abspath(repo)
+ self.repomdxml = os.path.join(self.repodir, 'repomd.xml')
+ self.checksum_type = 'sha256'
+
+ if not os.path.exists(self.repomdxml):
+ raise MDError, '%s not found' % self.repomdxml
+
+ try:
+ self.repoobj = RepoMD(self.repodir)
+ self.repoobj.parse(self.repomdxml)
+ except RepoMDError, e:
+ raise MDError, 'Could not parse %s' % self.repomdxml
+
+
+ def add(self, metadata, mdtype=None):
+ """ Insert arbitrary metadata into this repository.
+ metadata can be either an xml.dom.minidom.Document object, or
+ a filename.
+ """
+ md = None
+ if not metadata:
+ raise MDError, 'metadata cannot be None'
+ if isinstance(metadata, minidom.Document):
+ md = metadata.toxml()
+ mdname = 'updateinfo.xml'
+ elif isinstance(metadata, str):
+ if os.path.exists(metadata):
+ if metadata.endswith('.gz'):
+ oldmd = GzipFile(filename=metadata, mode='rb')
+ else:
+ oldmd = file(metadata, 'r')
+ md = oldmd.read()
+ oldmd.close()
+ mdname = os.path.basename(metadata)
+ else:
+ raise MDError, '%s not found' % metadata
+ else:
+ raise MDError, 'invalid metadata type'
+
+ ## Compress the metadata and move it into the repodata
+ if not mdname.endswith('.gz'):
+ mdname += '.gz'
+ if not mdtype:
+ mdtype = mdname.split('.')[0]
+
+ destmd = os.path.join(self.repodir, mdname)
+ newmd = GzipFile(filename=destmd, mode='wb')
+ newmd.write(md)
+ newmd.close()
+ print "Wrote:", destmd
+
+ open_csum = checksum(self.checksum_type, metadata)
+ csum, destmd = checksum_and_rename(destmd, self.checksum_type)
+ base_destmd = os.path.basename(destmd)
+
+
+ ## Remove any stale metadata
+ if mdtype in self.repoobj.repoData:
+ del self.repoobj.repoData[mdtype]
+
+
+ new_rd = RepoData()
+ new_rd.type = mdtype
+ new_rd.location = (None, 'repodata/' + base_destmd)
+ new_rd.checksum = (self.checksum_type, csum)
+ new_rd.openchecksum = (self.checksum_type, open_csum)
+ new_rd.size = str(os.stat(destmd).st_size)
+ new_rd.timestamp = str(os.stat(destmd).st_mtime)
+ self.repoobj.repoData[new_rd.type] = new_rd
+
+ print " type =", new_rd.type
+ print " location =", new_rd.location[1]
+ print " checksum =", new_rd.checksum[1]
+ print " timestamp =", new_rd.timestamp
+ print " open-checksum =", new_rd.openchecksum[1]
+
+ ## Write the updated repomd.xml
+ outmd = file(self.repomdxml, 'w')
+ outmd.write(self.repoobj.dump_xml())
+ outmd.close()
+ print "Wrote:", self.repomdxml
+
+
+def main(args):
+ parser = OptionParser(version='modifyrepo version %s' % __version__)
+ # query options
+ parser.add_option("--mdtype", dest='mdtype',
+ help="specific datatype of the metadata, will be derived from the filename if not specified")
+ parser.usage = "modifyrepo [options] <input_metadata> <output repodata>"
+
+ (opts, argsleft) = parser.parse_args(args)
+ if len(argsleft) != 2:
+ parser.print_usage()
+ return 0
+ metadata = argsleft[0]
+ repodir = argsleft[1]
+ try:
+ repomd = RepoMetadata(repodir)
+ except MDError, e:
+ print "Could not access repository: %s" % str(e)
+ return 1
+ try:
+ repomd.add(metadata, mdtype=opts.mdtype)
+ except MDError, e:
+ print "Could not add metadata from file %s: %s" % (metadata, str(e))
+ return 1
+
+if __name__ == '__main__':
+ ret = main(sys.argv[1:])
+ sys.exit(ret)
diff --git a/worker.py b/worker.py
new file mode 100755
index 0000000..eb35ef7
--- /dev/null
+++ b/worker.py
@@ -0,0 +1,99 @@
+#!/usr/bin/python -tt
+
+import sys
+import yum
+import createrepo
+import os
+import rpmUtils
+from optparse import OptionParser
+
+
+# pass in dir to make tempdirs in
+# make tempdir for this worker
+# create 3 files in that tempdir
+# return how many pkgs
+# return on stderr where things went to hell
+
+#TODO - take most of read_in_package from createrepo and duplicate it here
+# so we can do downloads, etc.
+# then replace callers of read_in_package with forked callers of this
+# and reassemble at the end
+
+def main(args):
+ parser = OptionParser()
+ parser.add_option('--tmpmdpath', default=None,
+ help="path where the outputs should be dumped for this worker")
+ parser.add_option("--pkgoptions", default=[], action='append',
+ help="pkgoptions in the format of key=value")
+ parser.add_option("--quiet", default=False, action='store_true',
+ help="only output errors and a total")
+ parser.add_option("--verbose", default=False, action='store_true',
+ help="output errors and a total")
+ parser.add_option("--globalopts", default=[], action='append',
+ help="general options in the format of key=value")
+
+
+ opts, pkgs = parser.parse_args(args)
+ external_data = {'_packagenumber': 1}
+ globalopts = {}
+ if not opts.tmpmdpath:
+ print >> sys.stderr, "tmpmdpath required for destination files"
+ sys.exit(1)
+
+
+ for strs in opts.pkgoptions:
+ k,v = strs.split('=')
+ if v in ['True', 'true', 'yes', '1', 1]:
+ v = True
+ elif v in ['False', 'false', 'no', '0', 0]:
+ v = False
+ elif v in ['None', 'none', '']:
+ v = None
+ external_data[k] = v
+
+ for strs in opts.globalopts:
+ k,v = strs.split('=')
+ if v in ['True', 'true', 'yes', '1', 1]:
+ v = True
+ elif v in ['False', 'false', 'no', '0', 0]:
+ v = False
+ elif v in ['None', 'none', '']:
+ v = None
+ globalopts[k] = v
+
+
+ reldir = external_data['_reldir']
+ ts = rpmUtils.transaction.initReadOnlyTransaction()
+ pri = open(opts.tmpmdpath + '/primary.xml' , 'w')
+ fl = open(opts.tmpmdpath + '/filelists.xml' , 'w')
+ other = open(opts.tmpmdpath + '/other.xml' , 'w')
+
+
+ for pkgfile in pkgs:
+ pkgpath = reldir + '/' + pkgfile
+ if not os.path.exists(pkgpath):
+ print >> sys.stderr, "File not found: %s" % pkgpath
+ continue
+
+ try:
+ if not opts.quiet and opts.verbose:
+ print "reading %s" % (pkgfile)
+
+ pkg = createrepo.yumbased.CreateRepoPackage(ts, package=pkgpath,
+ external_data=external_data)
+ pri.write(pkg.xml_dump_primary_metadata())
+ fl.write(pkg.xml_dump_filelists_metadata())
+ other.write(pkg.xml_dump_other_metadata(clog_limit=
+ globalopts.get('clog_limit', None)))
+ except yum.Errors.YumBaseError, e:
+ print >> sys.stderr, "Error: %s" % e
+ continue
+ else:
+ external_data['_packagenumber']+=1
+
+ pri.close()
+ fl.close()
+ other.close()
+
+if __name__ == "__main__":
+ main(sys.argv[1:])