Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24b259461d | ||
|
|
5b0991ef7f | ||
|
|
1e0aa5688e | ||
|
|
59e136dd62 | ||
|
|
279d081abc | ||
|
|
08e4c40e5f | ||
|
|
a4fd030521 | ||
|
|
165eece6da | ||
|
|
111ceaae64 | ||
|
|
8705543450 | ||
|
|
11a431f64f | ||
|
|
ccb8c0d805 | ||
|
|
661ee8f5ce | ||
|
|
66d8cff3c2 | ||
|
|
9f7c343887 | ||
|
|
ed1d53e230 | ||
|
|
20317453d2 | ||
|
|
ee1adc1a30 | ||
|
|
b7c16bfd25 | ||
|
|
73f3d7f470 | ||
|
|
9a51e1e208 | ||
|
|
200ce7c759 | ||
|
|
341775b106 | ||
|
|
199f0776f0 | ||
|
|
fe3a588bb8 | ||
|
|
dff52419e8 | ||
|
|
3ef83ae349 | ||
|
|
9fe33c71ca | ||
|
|
8b5d640dfc | ||
|
|
9f7a15eecc | ||
|
|
ea6b24d33e | ||
|
|
abff05d844 | ||
|
|
f7007fdd46 | ||
|
|
9e788bbbd9 | ||
|
|
b3a030dcd1 | ||
|
|
7199cf60bf | ||
|
|
9044c8c267 | ||
|
|
6d36b6063b | ||
|
|
11097febd0 | ||
|
|
274b499c11 | ||
|
|
d1bea4f005 | ||
|
|
d276dc3b78 | ||
|
|
1d13a5d8f7 | ||
|
|
2f69b507c7 | ||
|
|
55c56aa7c6 | ||
|
|
a74427b99f | ||
|
|
dd677777e5 | ||
|
|
2d2d92cd83 | ||
|
|
213ce45335 | ||
|
|
72c90f3cca | ||
|
|
a1a6150bea | ||
|
|
57544ec5ed | ||
|
|
9eabca5907 | ||
|
|
b393c428cf | ||
|
|
616b49cd71 | ||
|
|
a157fb63ad | ||
|
|
5b5bcf8c51 | ||
|
|
e1bd33f5e5 | ||
|
|
4694b35f89 | ||
|
|
5963444b1b | ||
|
|
1efddbd4cb | ||
|
|
b99de6c069 | ||
|
|
f826db0420 | ||
|
|
bd25e7101d | ||
|
|
32d4a33b6a | ||
|
|
f5485201fb | ||
|
|
e30ccdc0b6 | ||
|
|
dfa3cbea14 |
@@ -1,339 +1,165 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
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 Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
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.
|
||||
0. Additional Definitions.
|
||||
|
||||
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.
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
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.
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
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.
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
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.
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
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 "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
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".
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
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.
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
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.
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
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.
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
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:
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
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.
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
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.)
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
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.
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
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.
|
||||
4. Combined Works.
|
||||
|
||||
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.
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
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) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
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 the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
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) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
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.)
|
||||
d) Do one of the following:
|
||||
|
||||
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.
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
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.
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
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.
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
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.
|
||||
5. Combined Libraries.
|
||||
|
||||
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.
|
||||
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 that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
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.
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
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.
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
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.
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU 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.
|
||||
|
||||
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.
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
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) <year> <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.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) year 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 Lesser General
|
||||
Public License instead of this License.
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
ugit: A PyQt-based git gui
|
||||
|
||||
Copyright (C) 2007 David Aguilar
|
||||
Copyright (C) 2008 David Aguilar
|
||||
|
||||
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
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
@@ -12,22 +12,46 @@ 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
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
REQUIREMENTS
|
||||
|
||||
Python 2.4 or newer
|
||||
http://python.org/
|
||||
|
||||
GitPython
|
||||
http://gitorious.org/projects/git-python/
|
||||
|
||||
PyQt 4.3 or newer
|
||||
http://www.riverbankcomputing.co.uk/software/pyqt/intro
|
||||
|
||||
|
||||
INSTALLATION
|
||||
|
||||
If you downloaded a tarball, add /path/to/ugit/bin
|
||||
to your $PATH and type:
|
||||
Debian and RPM users can download freshly built binaries here:
|
||||
|
||||
http://ugit.sf.net/
|
||||
|
||||
sudo dpkg -i ugit_*.deb
|
||||
sudo rpm -ivh ugit-*.rpm
|
||||
|
||||
|
||||
If you downloaded a tarball, make sure /path/to/ugit/bin is in your $PATH.
|
||||
|
||||
|
||||
HOW TO RUN
|
||||
|
||||
Take your pick--any of the following incantations work from the command line:
|
||||
|
||||
git ugit
|
||||
git-ugit
|
||||
ugit
|
||||
|
||||
Launching ugit on repositories outside your $CWD:
|
||||
|
||||
git ugit /repo/path-1 /repo/path-2 .. /repo/path-N
|
||||
|
||||
|
||||
BUILDING FROM SOURCE
|
||||
@@ -43,8 +67,6 @@ Synopsis:
|
||||
make
|
||||
sudo make install
|
||||
|
||||
cd my/git/repo
|
||||
git ugit
|
||||
|
||||
WINDOWS SUPPORT
|
||||
|
||||
@@ -65,12 +87,17 @@ as of late 2007:
|
||||
http://git.or.cz/gitwiki/WindowsInstall
|
||||
|
||||
4) Download and unpack ugit:
|
||||
|
||||
http://ugit.sf.net/releases/
|
||||
|
||||
Grab the latest win32 tarball and run ugit-win32.sh from
|
||||
within an msysgit or cygwin shell.
|
||||
|
||||
The script assumes the you have Python/PyQt4 installed in C:/Python25.
|
||||
If this is not the case, edit the script accordingly.
|
||||
If this is not the case simply edit the script accordingly.
|
||||
|
||||
voila, ugit on Windows.
|
||||
|
||||
|
||||
NOTE: Windows is not the developer's primary platform.
|
||||
Patches are always greatly appreciated.
|
||||
|
||||
@@ -95,36 +95,37 @@ generateMakefile()
|
||||
# Waf Makefile wrapper
|
||||
# http://www.freehackers.org/~tnagy/waf.html
|
||||
WAF_HOME=$CUR_DIR
|
||||
PYTHON=$PYTHON
|
||||
|
||||
all:
|
||||
@$WAF build
|
||||
@\$(PYTHON) $WAF build
|
||||
|
||||
all_debug:
|
||||
@$WAF -v build
|
||||
@\$(PYTHON) $WAF -v build
|
||||
|
||||
all_progress:
|
||||
@$WAF -p build
|
||||
@\$(PYTHON) $WAF -p build
|
||||
|
||||
install:
|
||||
@$WAF install
|
||||
@\$(PYTHON) $WAF install
|
||||
|
||||
uninstall:
|
||||
@$WAF uninstall
|
||||
@\$(PYTHON) $WAF uninstall
|
||||
|
||||
clean:
|
||||
@$WAF clean
|
||||
@\$(PYTHON) $WAF clean
|
||||
|
||||
distclean:
|
||||
@$WAF distclean
|
||||
@\$(PYTHON) $WAF distclean
|
||||
@-rm -rf cache/
|
||||
@-rm -rf _build_
|
||||
@-rm -f Makefile
|
||||
|
||||
check:
|
||||
@$WAF check
|
||||
@\$(PYTHON) $WAF check
|
||||
|
||||
dist:
|
||||
@$WAF dist
|
||||
@\$(PYTHON) $WAF dist
|
||||
|
||||
test:
|
||||
@$TESTDIR/run_tests
|
||||
@@ -140,10 +141,6 @@ $WAF configure $* || exit $EXIT_ERROR
|
||||
|
||||
#create a Makefile if waf configure succeeds
|
||||
if [ -f "${WORKINGDIR}/.lock-wscript" ] ; then
|
||||
if [ -f "Makefile" ] ; then
|
||||
echo ""
|
||||
else
|
||||
generateMakefile
|
||||
fi
|
||||
generateMakefile
|
||||
fi
|
||||
exit $EXIT_SUCCESS
|
||||
|
||||
Vendored
+62
@@ -1,3 +1,63 @@
|
||||
ugit (0.9.3-0) unstable; urgency=low
|
||||
|
||||
ugit: now uses GitPython http://gitorious.org/projects/git-python
|
||||
|
||||
-- David Aguilar <davvid@gmail.com> Sun, 01 Jun 2008 12:00:00 -0700
|
||||
|
||||
ugit (0.9.2-3) unstable; urgency=low
|
||||
|
||||
controllers: fix hang on exit bug
|
||||
export-patches: use the updated format_patch_helper
|
||||
python-git: make format_patch_helper better
|
||||
git.py: make "output" a keyword argument
|
||||
controllers: fix format_patch_helper usage
|
||||
git.py: fix format_patch_helper
|
||||
debian: update the changelog
|
||||
gitmodel: ensure we use Model.clone() instead of clone from git.py
|
||||
git.py: add the "rest" of the git commands
|
||||
push: rename model params to avoid conflicting with git.py
|
||||
git.py: add a docstring to the git_cmd closure
|
||||
configure: allow $PYTHON to be overridden in the generated Makefile
|
||||
README: update usage and mention the deb/rpms
|
||||
wscript: only check env['PYTHON'] when python versions do not match
|
||||
git-ugit: setup sys.path for git.py
|
||||
python-git: fixed a bug in run_cmd
|
||||
python-git: handle filenames with embedded quotes in their names
|
||||
models: move the config functions closer to each other
|
||||
models: add a comment, fix a typo
|
||||
configure: allow fine-grained control over python versions
|
||||
configure: always regenerate the Makefile
|
||||
git.py: update docstring for run_cmd
|
||||
debian: install git.py for both python2.4 and python2.5
|
||||
build: add a pycompile shell script
|
||||
debian: update changelog with proper signature
|
||||
gitmodel: ensure we use Model.clone() instead of clone from git.py
|
||||
git.py: add the "rest" of the git commands
|
||||
|
||||
-- David Aguilar <davvid@gmail.com> Sat, 31 May 2008 12:00:00 -0700
|
||||
|
||||
ugit (0.9.2-2) unstable; urgency=low
|
||||
|
||||
push: rename model params to avoid conflicting with git.py
|
||||
git.py: add a docstring to the git_cmd closure
|
||||
configure: allow $PYTHON to be overridden in the generated Makefile
|
||||
README: update usage and mention the deb/rpms
|
||||
wscript: only check env['PYTHON'] when python versions do not match
|
||||
git-ugit: setup sys.path for git.py
|
||||
python-git: fixed a bug in run_cmd
|
||||
python-git: handle filenames with embedded quotes in their names
|
||||
models: move the config functions closer to each other
|
||||
models: add a comment, fix a typo
|
||||
configure: allow fine-grained control over python versions
|
||||
configure: always regenerate the Makefile
|
||||
git.py: update docstring for run_cmd
|
||||
debian: install git.py for both python2.4 and python2.5
|
||||
build: add a pycompile shell script
|
||||
debian: update changelog with proper signature
|
||||
install git.py to both python2.4 and python2.5 site-packages directories.
|
||||
|
||||
-- David Aguilar <davvid@gmail.com> Tue, 07 May 2008 12:00:00 -0700
|
||||
|
||||
ugit (0.9.2-1) unstable; urgency=low
|
||||
|
||||
python: make git.py globally accessible
|
||||
@@ -11,6 +71,8 @@ ugit (0.9.2-1) unstable; urgency=low
|
||||
tests: add an import test, update the model api in model-save-restore
|
||||
model: load() should import parameters in-place
|
||||
|
||||
-- David Aguilar <davvid@gmail.com> Mon, 05 May 2008 12:00:00 -0700
|
||||
|
||||
ugit (0.9.1-1) unstable; urgency=low
|
||||
|
||||
updated version script
|
||||
|
||||
Vendored
+2
-2
@@ -2,10 +2,10 @@ Source: ugit
|
||||
Section: devel
|
||||
Priority: extra
|
||||
Maintainer: David Aguilar <davvid@gmail.com>
|
||||
Build-Depends: debhelper (>= 5), git-core (>= 1.5), python (>= 2.4), gettext, pyqt4-dev-tools
|
||||
Build-Depends: debhelper (>= 5), git-core (>= 1.5), python-all-dev, python-git (>= 0.3), gettext, pyqt4-dev-tools
|
||||
Standards-Version: 3.7.2
|
||||
|
||||
Package: ugit
|
||||
Architecture: all
|
||||
Depends: python (>= 2.4), python-qt4 (>= 4.3), git-core (>= 1.5)
|
||||
Depends: python (>= 2.4), python-qt4 (>= 4.3), git-core (>= 1.5), python-git (>= 0.3)
|
||||
Description: ugit: the pythonic git gui
|
||||
|
||||
Vendored
+2
-2
@@ -13,14 +13,14 @@ BUILD=$(DEBIAN)/bld
|
||||
|
||||
configure: $(BUILD)/configure-stamp
|
||||
$(BUILD)/configure-stamp:
|
||||
env ./configure \
|
||||
./configure \
|
||||
--prefix=/usr \
|
||||
--blddir=$(BUILD) \
|
||||
--destdir=$(DEBIAN)/ugit
|
||||
touch $(BUILD)/configure-stamp
|
||||
|
||||
build: $(BUILD)/build-stamp
|
||||
$(BUILD)/build-stamp: $(BUILD)/configure-stamp
|
||||
$(BUILD)/build-stamp: $(BUILD)/configure-stamp
|
||||
$(MAKE)
|
||||
touch $(BUILD)/build-stamp
|
||||
|
||||
|
||||
@@ -1,517 +0,0 @@
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import types
|
||||
from cStringIO import StringIO
|
||||
DIFF_CONTEXT = 3
|
||||
|
||||
def set_diff_context(ctxt):
|
||||
global DIFF_CONTEXT
|
||||
DIFF_CONTEXT = ctxt
|
||||
|
||||
def get_tmp_filename():
|
||||
# Allow TMPDIR/TMP with a fallback to /tmp
|
||||
env = os.environ
|
||||
return os.path.join(env.get('TMP', env.get('TMPDIR', '/tmp')),
|
||||
'.git.%s.%s' % ( os.getpid(), time.time()))
|
||||
|
||||
def run_cmd(cmd, *args, **kwargs):
|
||||
"""
|
||||
Returns an array of strings from the command's output.
|
||||
defaults:
|
||||
raw: off -> passing raw=True returns a string instead of a list of strings.
|
||||
with_status: off -> passing with_status=True returns
|
||||
tuple(status,output) instead of just output
|
||||
|
||||
run_command("git foo", bar, buzz, baz=value, q=True)
|
||||
implies:
|
||||
argv=["git","foo","-q","--baz=value","bar","buzz"]
|
||||
"""
|
||||
kwarglist = []
|
||||
for k,v in kwargs.iteritems():
|
||||
if len(k) > 1:
|
||||
k = k.replace('_','-')
|
||||
if v is True:
|
||||
kwarglist.append("--%s" % k)
|
||||
elif v is not None and type(v) is not bool:
|
||||
kwarglist.append("--%s=%s" % (k,v))
|
||||
else:
|
||||
if v is True:
|
||||
kwarglist.append("-%s" % k)
|
||||
elif v is not None and type(v) is not bool:
|
||||
kwarglist.append("-%s" % k)
|
||||
kwarglist.append(str(v))
|
||||
# Handle cmd as either a string or an argv list
|
||||
if type(cmd) is str:
|
||||
# we only call run_cmd(str) with str='git command'
|
||||
# or other simple commands
|
||||
cmd = cmd.split(' ')
|
||||
cmd += kwarglist
|
||||
cmd += tuple(args)
|
||||
else:
|
||||
cmd = tuple(cmd + kwarglist + list(args))
|
||||
|
||||
def pop_key(d, key):
|
||||
val = d.get(key)
|
||||
try: del d[key]
|
||||
except: pass
|
||||
return val
|
||||
raw = pop_key(kwargs, 'raw')
|
||||
with_status = pop_key(kwargs,'with_status')
|
||||
with_stderr = not pop_key(kwargs,'without_stderr')
|
||||
cwd = os.getcwd()
|
||||
stderr = None
|
||||
if with_stderr:
|
||||
stderr = subprocess.STDOUT
|
||||
# start the process
|
||||
proc = subprocess.Popen(cmd, cwd=cwd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=stderr,
|
||||
stdin=None)
|
||||
# Wait for the process to return
|
||||
output, err = proc.communicate()
|
||||
# conveniently strip off trailing newlines
|
||||
if not raw:
|
||||
output = output.rstrip()
|
||||
if err:
|
||||
raise RuntimeError("%s return exit status %d"
|
||||
% ( str(cmd), err ))
|
||||
if with_status:
|
||||
return (err, output)
|
||||
else:
|
||||
return output
|
||||
|
||||
# union of functions in this file and dynamic functions
|
||||
# defined in the git command string list below
|
||||
def git(*args,**kwargs):
|
||||
"""This is a convenience wrapper around run_cmd that
|
||||
sets things up so that commands are run in the canonical
|
||||
'git command [options] [args]' form."""
|
||||
cmd = 'git %s' % args[0]
|
||||
return run_cmd(cmd, *args[1:], **kwargs)
|
||||
|
||||
class GitCommand(object):
|
||||
"""This class wraps this module so that arbitrary git commands
|
||||
can be dynamically called at runtime."""
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.commands = {}
|
||||
# This creates git.foo() methods dynamically for each of the
|
||||
# following names at import-time.
|
||||
for cmd in """
|
||||
add
|
||||
apply
|
||||
branch
|
||||
checkout
|
||||
cherry_pick
|
||||
commit
|
||||
diff
|
||||
fetch
|
||||
format_patch
|
||||
grep
|
||||
log
|
||||
ls_tree
|
||||
merge
|
||||
pull
|
||||
push
|
||||
rebase
|
||||
remote
|
||||
reset
|
||||
read_tree
|
||||
rev_list
|
||||
rm
|
||||
show
|
||||
status
|
||||
tag
|
||||
""".split(): getattr(self, cmd)
|
||||
|
||||
def setup_commands(self):
|
||||
# Import the functions from the module
|
||||
for name, val in self.module.__dict__.iteritems():
|
||||
if type(val) is types.FunctionType:
|
||||
setattr(self, name, val)
|
||||
# Import dynamic functions and those from the module
|
||||
# functions into self.commands
|
||||
for name, val in self.__dict__.iteritems():
|
||||
if type(val) is types.FunctionType:
|
||||
self.commands[name] = val
|
||||
|
||||
def __getattr__(self, name):
|
||||
if hasattr(self.module, name):
|
||||
value = getattr(self.module, name)
|
||||
setattr(self, name, value)
|
||||
return value
|
||||
def git_cmd(*args, **kwargs):
|
||||
return git(name.replace('_','-'), *args, **kwargs)
|
||||
setattr(self, name, git_cmd)
|
||||
return git_cmd
|
||||
|
||||
# core git wrapper for use in this module
|
||||
gitcmd = GitCommand(sys.modules[__name__])
|
||||
sys.modules[__name__] = gitcmd
|
||||
|
||||
#+-------------------------------------------------------------------------
|
||||
#+ A regex for matching the output of git(log|rev-list) --pretty=oneline
|
||||
REV_LIST_REGEX = re.compile('([0-9a-f]+)\W(.*)')
|
||||
|
||||
def abort_merge():
|
||||
# Reset the worktree
|
||||
output = gitcmd.read_tree("HEAD", reset=True, u=True, v=True)
|
||||
# remove MERGE_HEAD
|
||||
merge_head = git_repo_path('MERGE_HEAD')
|
||||
if os.path.exists(merge_head):
|
||||
os.unlink(merge_head)
|
||||
# remove MERGE_MESSAGE, etc.
|
||||
merge_msg_path = get_merge_message_path()
|
||||
while merge_msg_path is not None:
|
||||
os.unlink(merge_msg_path)
|
||||
merge_msg_path = get_merge_message_path()
|
||||
|
||||
def add_or_remove(*to_process):
|
||||
"""Invokes 'git add' to index the filenames in to_process that exist
|
||||
and 'git rm' for those that do not exist."""
|
||||
|
||||
if not to_process:
|
||||
return 'No files to add or remove.'
|
||||
|
||||
to_add = []
|
||||
to_remove = []
|
||||
|
||||
for filename in to_process:
|
||||
if os.path.exists(filename):
|
||||
to_add.append(filename)
|
||||
|
||||
output = gitcmd.add(verbose=True, *to_add)
|
||||
|
||||
if len(to_add) == len(to_process):
|
||||
# to_process only contained unremoved files --
|
||||
# short-circuit the removal checks
|
||||
return output
|
||||
|
||||
# Process files to remote
|
||||
for filename in to_process:
|
||||
if not os.path.exists(filename):
|
||||
to_remove.append(filename)
|
||||
output + '\n\n' + gitcmd.rm(*to_remove)
|
||||
|
||||
def branch_list(remote=False):
|
||||
branches = map(lambda x: x.lstrip('* '),
|
||||
gitcmd.branch(r=remote).splitlines())
|
||||
if remote:
|
||||
remotes = []
|
||||
for branch in branches:
|
||||
if branch.endswith('/HEAD'):
|
||||
continue
|
||||
remotes.append(branch)
|
||||
return remotes
|
||||
return branches
|
||||
|
||||
def cherry_pick_list(revs, **kwargs):
|
||||
"""Cherry-picks each revision into the current branch.
|
||||
Returns a list of command output strings (1 per cherry pick)"""
|
||||
if not revs:
|
||||
return []
|
||||
cherries = []
|
||||
for rev in revs:
|
||||
cherries.append(gitcmd.cherry_pick(rev, **kwargs))
|
||||
return '\n'.join(cherries)
|
||||
|
||||
def commit_with_msg(msg, amend=False):
|
||||
"""Creates a git commit."""
|
||||
|
||||
if not msg.endswith('\n'):
|
||||
msg += '\n'
|
||||
# Sure, this is a potential "security risk," but if someone
|
||||
# is trying to intercept/re-write commit messages on your system,
|
||||
# then you probably have bigger problems to worry about.
|
||||
tmpfile = get_tmp_filename()
|
||||
kwargs = {
|
||||
'F': tmpfile,
|
||||
'amend': amend,
|
||||
}
|
||||
# Create the commit message file
|
||||
file = open(tmpfile, 'w')
|
||||
file.write(msg)
|
||||
file.close()
|
||||
|
||||
# Run 'git commit'
|
||||
output = gitcmd.commit(F=tmpfile, amend=amend)
|
||||
os.unlink(tmpfile)
|
||||
|
||||
return ('git commit -F %s --amend %s\n\n%s'
|
||||
% ( tmpfile, amend, output ))
|
||||
|
||||
def create_branch(name, base, track=False):
|
||||
"""Creates a branch starting from base. Pass track=True
|
||||
to create a remote tracking branch."""
|
||||
return gitcmd.branch(name, base, track=track)
|
||||
|
||||
def current_branch():
|
||||
"""Parses 'git branch' to find the current branch."""
|
||||
|
||||
branches = gitcmd.branch().splitlines()
|
||||
for branch in branches:
|
||||
if branch.startswith('* '):
|
||||
return branch.lstrip('* ')
|
||||
return 'Detached HEAD'
|
||||
|
||||
def diff_helper(commit=None,
|
||||
filename=None,
|
||||
color=False,
|
||||
cached=True,
|
||||
with_diff_header=False,
|
||||
suppress_header=True,
|
||||
reverse=False):
|
||||
"Invokes git diff on a filepath."
|
||||
|
||||
argv = []
|
||||
if commit:
|
||||
argv.append('%s^..%s' % (commit, commit))
|
||||
|
||||
if filename:
|
||||
argv.append('--')
|
||||
if type(filename) is list:
|
||||
argv.extend(filename)
|
||||
else:
|
||||
argv.append(filename)
|
||||
|
||||
diff = gitcmd.diff(
|
||||
R=reverse,
|
||||
color=color,
|
||||
cached=cached,
|
||||
patch_with_raw=True,
|
||||
unified=DIFF_CONTEXT,
|
||||
*argv
|
||||
).splitlines()
|
||||
|
||||
output = StringIO()
|
||||
start = False
|
||||
del_tag = 'deleted file mode '
|
||||
|
||||
headers = []
|
||||
deleted = cached and not os.path.exists(filename)
|
||||
for line in diff:
|
||||
if not start and '@@ ' in line and ' @@' in line:
|
||||
start = True
|
||||
if start or(deleted and del_tag in line):
|
||||
output.write(line + '\n')
|
||||
else:
|
||||
if with_diff_header:
|
||||
headers.append(line)
|
||||
elif not suppress_header:
|
||||
output.write(line + '\n')
|
||||
result = output.getvalue()
|
||||
output.close()
|
||||
if with_diff_header:
|
||||
return('\n'.join(headers), result)
|
||||
else:
|
||||
return result
|
||||
|
||||
def diffstat():
|
||||
return gitcmd.diff(
|
||||
'HEAD^',
|
||||
unified=DIFF_CONTEXT,
|
||||
stat=True)
|
||||
|
||||
def diffindex():
|
||||
return gitcmd.diff(
|
||||
unified=DIFF_CONTEXT,
|
||||
stat=True,
|
||||
cached=True)
|
||||
|
||||
def format_patch_helper(*revs):
|
||||
"""writes patches named by revs to the "patches" directory."""
|
||||
num_patches = 1
|
||||
output = []
|
||||
for idx, rev in enumerate(revs):
|
||||
real_idx = idx + num_patches
|
||||
revarg = '%s^..%s' % (rev,rev)
|
||||
output.append(
|
||||
gitcmd.format_patch(
|
||||
revarg,
|
||||
o='patches',
|
||||
start_number=real_idx,
|
||||
n=len(revs) > 1,
|
||||
thread=True,
|
||||
patch_with_stat=True
|
||||
)
|
||||
)
|
||||
num_patches += output[-1].count('\n')
|
||||
return '\n'.join(output)
|
||||
|
||||
def get_merge_message():
|
||||
return gitcmd.fmt_merge_msg('--file', git_repo_path('FETCH_HEAD'))
|
||||
|
||||
def config_dict(local=True):
|
||||
if local:
|
||||
argv = [ '--list' ]
|
||||
else:
|
||||
argv = ['--global', '--list' ]
|
||||
return config_to_dict(
|
||||
gitcmd.config(*argv).splitlines())
|
||||
|
||||
def config_set(key=None, value=None, local=True):
|
||||
if key and value is not None:
|
||||
# git config category.key value
|
||||
strval = str(value)
|
||||
if type(value) is bool:
|
||||
# git uses "true" and "false"
|
||||
strval = strval.lower()
|
||||
if local:
|
||||
argv = [ key, strval ]
|
||||
else:
|
||||
argv = [ '--global', key, strval ]
|
||||
return gitcmd.config(*argv)
|
||||
else:
|
||||
msg = "oops in git.config_set(key=%s,value=%s,local=%s"
|
||||
raise Exception(msg % (key, value, local))
|
||||
|
||||
def config_to_dict(config_lines):
|
||||
"""parses the lines from git config --list into a dictionary"""
|
||||
|
||||
newdict = {}
|
||||
for line in config_lines:
|
||||
k, v = line.split('=', 1)
|
||||
k = k.replace('.','_') # git -> model
|
||||
if v == 'true' or v == 'false':
|
||||
v = bool(eval(v.title()))
|
||||
try:
|
||||
v = int(eval(v))
|
||||
except:
|
||||
pass
|
||||
newdict[k]=v
|
||||
return newdict
|
||||
|
||||
def log_helper(all=False):
|
||||
"""Returns a pair of parallel arrays listing the revision sha1's
|
||||
and commit summaries."""
|
||||
revs = []
|
||||
summaries = []
|
||||
regex = REV_LIST_REGEX
|
||||
output = gitcmd.log(pretty='oneline', all=all)
|
||||
for line in output.splitlines():
|
||||
match = regex.match(line)
|
||||
if match:
|
||||
revs.append(match.group(1))
|
||||
summaries.append(match.group(2))
|
||||
return( revs, summaries )
|
||||
|
||||
def parse_ls_tree(rev):
|
||||
"""Returns a list of(mode, type, sha1, path) tuples."""
|
||||
lines = gitcmd.ls_tree(rev, r=True).splitlines()
|
||||
output = []
|
||||
regex = re.compile('^(\d+)\W(\w+)\W(\w+)[ \t]+(.*)$')
|
||||
for line in lines:
|
||||
match = regex.match(line)
|
||||
if match:
|
||||
mode = match.group(1)
|
||||
objtype = match.group(2)
|
||||
sha1 = match.group(3)
|
||||
filename = match.group(4)
|
||||
output.append((mode, objtype, sha1, filename,) )
|
||||
return output
|
||||
|
||||
def push_helper(remote, local_branch, remote_branch, ffwd=True, tags=False):
|
||||
if ffwd:
|
||||
branch_arg = '%s:%s' % ( local_branch, remote_branch )
|
||||
else:
|
||||
branch_arg = '+%s:%s' % ( local_branch, remote_branch )
|
||||
return gitcmd.push(remote, branch_arg, with_status=True, tags=tags)
|
||||
|
||||
def remote_url(name):
|
||||
return gitcmd.config('remote.%s.url' % name, get=True)
|
||||
|
||||
def rev_list_range(start, end):
|
||||
range = '%s..%s' % ( start, end )
|
||||
raw_revs = gitcmd.rev_list(range, pretty='oneline')
|
||||
return parse_rev_list(raw_revs)
|
||||
|
||||
def git_repo_path(*subpaths):
|
||||
paths = [ gitcmd.rev_parse(git_dir=True) ]
|
||||
paths.extend(subpaths)
|
||||
return os.path.realpath(os.path.join(*paths))
|
||||
|
||||
def get_merge_message_path():
|
||||
for file in ('MERGE_MSG', 'SQUASH_MSG'):
|
||||
path = git_repo_path(file)
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
return None
|
||||
|
||||
def reset_helper(*args, **kwargs):
|
||||
return gitcmd.reset('--', *args, **kwargs)
|
||||
|
||||
def parse_rev_list(raw_revs):
|
||||
revs = []
|
||||
for line in raw_revs.splitlines():
|
||||
match = REV_LIST_REGEX.match(line)
|
||||
if match:
|
||||
rev_id = match.group(1)
|
||||
summary = match.group(2)
|
||||
revs.append((rev_id, summary,) )
|
||||
return revs
|
||||
|
||||
def parse_status():
|
||||
"""RETURNS: A tuple of staged, unstaged and untracked file lists."""
|
||||
|
||||
MODIFIED_TAG = '# Changed but not updated:'
|
||||
UNTRACKED_TAG = '# Untracked files:'
|
||||
|
||||
RGX_RENAMED = re.compile(
|
||||
'(#\trenamed:\s+)'
|
||||
'(.*?)\s->\s(.*)'
|
||||
)
|
||||
|
||||
RGX_MODIFIED = re.compile(
|
||||
'(#\tmodified:\s+'
|
||||
'|#\tnew file:\s+'
|
||||
'|#\tdeleted:\s+)'
|
||||
)
|
||||
staged = []
|
||||
unstaged = []
|
||||
untracked = []
|
||||
|
||||
STAGED_MODE = 0
|
||||
UNSTAGED_MODE = 1
|
||||
UNTRACKED_MODE = 2
|
||||
|
||||
mode = STAGED_MODE
|
||||
current_dest = staged
|
||||
|
||||
for status_line in gitcmd.status().splitlines():
|
||||
if status_line == MODIFIED_TAG:
|
||||
mode = UNSTAGED_MODE
|
||||
current_dest = unstaged
|
||||
continue
|
||||
|
||||
elif status_line == UNTRACKED_TAG:
|
||||
mode = UNTRACKED_MODE
|
||||
current_dest = untracked
|
||||
continue
|
||||
|
||||
# Staged/unstaged modified/renamed/deleted files
|
||||
if mode == STAGED_MODE or mode == UNSTAGED_MODE:
|
||||
match = RGX_MODIFIED.match(status_line)
|
||||
if match:
|
||||
tag = match.group(0)
|
||||
filename = status_line.replace(tag, '')
|
||||
current_dest.append(filename)
|
||||
continue
|
||||
match = RGX_RENAMED.match(status_line)
|
||||
if match:
|
||||
oldname = match.group(2)
|
||||
newname = match.group(3)
|
||||
current_dest.append(oldname)
|
||||
current_dest.append(newname)
|
||||
continue
|
||||
# Untracked files
|
||||
elif mode is UNTRACKED_MODE:
|
||||
if status_line.startswith('#\t'):
|
||||
current_dest.append(status_line[2:])
|
||||
|
||||
return( staged, unstaged, untracked )
|
||||
|
||||
# Must be executed after all functions are defined
|
||||
gitcmd.setup_commands()
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
from distutils.sysconfig import get_python_version
|
||||
|
||||
# Python site-packages helper
|
||||
def get_site_packages(prefix):
|
||||
python_api = 'python' + get_python_version()
|
||||
return os.path.join(prefix, 'lib', python_api, 'site-packages')
|
||||
|
||||
def configure(conf):
|
||||
conf.check_tool('python')
|
||||
prefix = conf.env['PREFIX']
|
||||
conf.env['PYTHON_SITE_PACKAGES'] = get_site_packages(prefix)
|
||||
|
||||
def build(bld):
|
||||
# Compile all Python modules
|
||||
pyqt = bld.create_obj('py')
|
||||
pyqt.inst_var = 'PYTHON_SITE_PACKAGES'
|
||||
pyqt.find_sources_in_dirs('.')
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
import py_compile
|
||||
for filename in sys.argv[1:]:
|
||||
py_compile.compile(filename, doraise=True)
|
||||
py_compile.compile(filename, doraise=True)
|
||||
@@ -5,25 +5,23 @@ import unittest
|
||||
import testutils
|
||||
from testmodel import TestModel
|
||||
|
||||
from ugit import git
|
||||
import git
|
||||
|
||||
class GitCommandTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.git = git.Git()
|
||||
|
||||
def testGitVersion(self):
|
||||
version = git.version()
|
||||
version = self.git.version()
|
||||
self.failUnless( version.startswith('git version') )
|
||||
|
||||
def testGitTag(self):
|
||||
tags = git.tag().splitlines()
|
||||
tags = self.git.tag().splitlines()
|
||||
self.failUnless( 'v0.0' in tags )
|
||||
|
||||
def testGitShowCdUp(self):
|
||||
os.chdir(testutils.TEST_SCRIPT_DIR)
|
||||
cdup = git.rev_parse(show_cdup=True)
|
||||
self.failUnless( cdup == '../' )
|
||||
|
||||
def testGitShow(self):
|
||||
id = '1b9742bda5d26a4f250fa64657f66ed20624a084'
|
||||
contents = git.show(id).splitlines()
|
||||
contents = self.git.show(id).splitlines()
|
||||
self.failUnless( contents[0] == '/build' )
|
||||
|
||||
|
||||
|
||||
+13
-9
@@ -5,17 +5,20 @@ import unittest
|
||||
import testutils
|
||||
from testutils import pipe
|
||||
|
||||
from ugit import git
|
||||
from ugit import models
|
||||
|
||||
class GitOpsTest(testutils.TestCase):
|
||||
|
||||
def testCommit(self):
|
||||
self.shell("""
|
||||
echo A > A
|
||||
echo B > B
|
||||
git init 2>&1 >/dev/null
|
||||
git init 2>&1 > /dev/null
|
||||
git add A B
|
||||
""")
|
||||
git.commit(m="commit test")
|
||||
|
||||
model = models.Model()
|
||||
model.commit(m="commit test")
|
||||
log = pipe("git log --pretty=oneline | wc -l")
|
||||
|
||||
self.failUnless( '1' == log )
|
||||
@@ -25,25 +28,26 @@ class GitOpsTest(testutils.TestCase):
|
||||
git init 2>&1 >/dev/null
|
||||
git config section.key value
|
||||
""")
|
||||
value = git.config('section.key', get=True)
|
||||
model = models.Model()
|
||||
value = model.config('section.key', get=True)
|
||||
|
||||
self.failUnless( value == 'value' )
|
||||
|
||||
# Test config_set
|
||||
git.config_set('section.bool', True)
|
||||
value = git.config('section.bool', get=True)
|
||||
model.config_set('section.bool', True)
|
||||
value = model.config('section.bool', get=True)
|
||||
|
||||
self.failUnless( value == 'true' )
|
||||
git.config_set('section.bool', False)
|
||||
model.config_set('section.bool', False)
|
||||
|
||||
# Test config_dict
|
||||
config_dict = git.config_dict(local=True)
|
||||
config_dict = model.config_dict(local=True)
|
||||
|
||||
self.failUnless( config_dict['section_key'] == 'value' )
|
||||
self.failUnless( config_dict['section_bool'] == False )
|
||||
|
||||
# Test config_dict --global
|
||||
global_dict = git.config_dict(local=False)
|
||||
global_dict = model.config_dict(local=False)
|
||||
|
||||
self.failUnless( type(global_dict) is dict )
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from ugit.qobserver import QObserver
|
||||
# controllers namespace
|
||||
import search
|
||||
from util import logger
|
||||
from push import push_branches
|
||||
from remote import remote_action
|
||||
from util import choose_branch
|
||||
from util import select_commits
|
||||
from util import update_options
|
||||
@@ -29,6 +29,7 @@ from merge import local_merge
|
||||
from merge import abort_merge
|
||||
from bookmark import save_bookmark
|
||||
from bookmark import manage_bookmarks
|
||||
from stash import stash
|
||||
|
||||
|
||||
class Controller(QObserver):
|
||||
@@ -69,7 +70,9 @@ class Controller(QObserver):
|
||||
signoff_button = self.model.add_signoff,
|
||||
stage_button = self.stage_selected,
|
||||
commit_button = self.commit,
|
||||
fetch_button = self.fetch,
|
||||
push_button = self.push,
|
||||
pull_button = self.pull,
|
||||
# List Widgets
|
||||
staged = self.diff_staged,
|
||||
unstaged = self.diff_unstaged,
|
||||
@@ -136,6 +139,8 @@ class Controller(QObserver):
|
||||
menu_show_diffstat = self.show_diffstat,
|
||||
menu_show_index = self.show_index,
|
||||
menu_export_patches = self.export_patches,
|
||||
menu_stash =
|
||||
lambda: stash( self.model, self.view ),
|
||||
menu_load_commitmsg = self.load_commitmsg,
|
||||
menu_cherry_pick = self.cherry_pick,
|
||||
menu_get_prev_commitmsg = model.get_prev_commitmsg,
|
||||
@@ -356,6 +361,51 @@ class Controller(QObserver):
|
||||
self.view.diff_dock.raise_()
|
||||
self.__diffgui_enabled = True
|
||||
|
||||
def edit_file(self, staged=True):
|
||||
self.__staged_diff_in_view = staged
|
||||
if self.__staged_diff_in_view:
|
||||
widget = self.view.staged
|
||||
else:
|
||||
widget = self.view.unstaged
|
||||
row, selected = qtutils.get_selected_row(widget)
|
||||
if not selected:
|
||||
return
|
||||
if staged:
|
||||
basename = self.model.get_staged()[row]
|
||||
else:
|
||||
basename = self.model.get_unstaged()[row]
|
||||
utils.fork(self.model.get_editor(), basename)
|
||||
|
||||
def edit_diff(self, staged=True):
|
||||
self.__staged_diff_in_view = staged
|
||||
if self.__staged_diff_in_view:
|
||||
widget = self.view.staged
|
||||
else:
|
||||
widget = self.view.unstaged
|
||||
row, selected = qtutils.get_selected_row(widget)
|
||||
diff, status = self.model.get_diff_and_status(row, staged=staged)
|
||||
if not selected:
|
||||
return
|
||||
|
||||
if staged:
|
||||
basename = self.model.get_staged()[row]
|
||||
else:
|
||||
basename = self.model.get_unstaged()[row]
|
||||
|
||||
contents = self.model.show("HEAD:"+basename, with_raw_output=True)
|
||||
tmpfile = self.model.get_tmp_filename()
|
||||
fh = open(tmpfile, 'w')
|
||||
fh.write(contents)
|
||||
fh.close()
|
||||
|
||||
pid = os.fork()
|
||||
if pid:
|
||||
return
|
||||
else:
|
||||
utils.run_cmd(self.model.get_diffeditor(), basename, tmpfile)
|
||||
os.unlink(tmpfile)
|
||||
sys.exit(0)
|
||||
|
||||
# use *rest to handle being called from different signals
|
||||
def diff_staged(self, *rest):
|
||||
self.view_diff(staged=True)
|
||||
@@ -366,10 +416,14 @@ class Controller(QObserver):
|
||||
|
||||
def export_patches(self):
|
||||
(revs, summaries) = self.model.log_helper()
|
||||
commits = self.select_commits_gui(self.tr('Export Patches'),
|
||||
to_export = self.select_commits_gui(self.tr('Export Patches'),
|
||||
revs, summaries)
|
||||
if not commits: return
|
||||
self.log(self.model.format_patch_helper(*commits))
|
||||
if not to_export:
|
||||
return
|
||||
to_export.reverse()
|
||||
revs.reverse()
|
||||
self.log(self.model.format_patch_helper(
|
||||
to_export, revs, output='patches'))
|
||||
|
||||
def open_repo(self):
|
||||
"""Spawns a new ugit session"""
|
||||
@@ -385,14 +439,14 @@ class Controller(QObserver):
|
||||
if self.model.save_at_exit():
|
||||
self.model.save_gui_settings()
|
||||
qtutils.close_log_window()
|
||||
self.view.hide()
|
||||
|
||||
if not self.inotify_thread: return
|
||||
if not self.inotify_thread.isRunning(): return
|
||||
if (self.inotify_thread and
|
||||
self.inotify_thread.isRunning()):
|
||||
|
||||
self.inotify_thread.abort = True
|
||||
self.inotify_thread.wait()
|
||||
sys.exit(0)
|
||||
self.inotify_thread.abort = True
|
||||
self.inotify_thread.wait()
|
||||
|
||||
self.view.close()
|
||||
|
||||
def load_commitmsg(self):
|
||||
file = qtutils.open_dialog(self.view,
|
||||
@@ -507,8 +561,14 @@ class Controller(QObserver):
|
||||
self.model.load_commitmsg(merge_msg_path)
|
||||
self.view.editor_dock.raise_()
|
||||
|
||||
def fetch(self):
|
||||
remote_action(self.model, self.view, "Fetch")
|
||||
|
||||
def push(self):
|
||||
push_branches(self.model, self.view)
|
||||
remote_action(self.model, self.view, "Push")
|
||||
|
||||
def pull(self):
|
||||
remote_action(self.model, self.view, "Pull")
|
||||
|
||||
def show_diffstat(self):
|
||||
"""Show the diffstat from the latest commit."""
|
||||
@@ -611,12 +671,12 @@ class Controller(QObserver):
|
||||
|
||||
def viz_all(self):
|
||||
"""Visualizes the entire git history using gitk."""
|
||||
browser = self.model.get_global_ugit_historybrowser()
|
||||
browser = self.model.get_history_browser()
|
||||
utils.fork(browser,'--all')
|
||||
|
||||
def viz_current(self):
|
||||
"""Visualizes the current branch's history using gitk."""
|
||||
browser = self.model.get_global_ugit_historybrowser()
|
||||
browser = self.model.get_history_browser()
|
||||
utils.fork(browser, self.model.get_currentbranch())
|
||||
|
||||
def move_event(self, event):
|
||||
@@ -663,6 +723,11 @@ class Controller(QObserver):
|
||||
self.tr('Stage Selected'), self.stage_selected)
|
||||
self.__undo_changes_action = menu.addAction(
|
||||
self.tr('Undo Local Changes'), self.undo_changes)
|
||||
self.__edit_file = menu.addAction(
|
||||
self.tr('Launch Editor'), lambda: self.edit_file(staged=False))
|
||||
self.__edit_diff = menu.addAction(
|
||||
self.tr('Launch Diff Editor'), lambda: self.edit_diff(staged=False))
|
||||
|
||||
self.connect(self.__unstaged_menu, 'aboutToShow()',
|
||||
self.unstaged_context_menu_about_to_show)
|
||||
|
||||
|
||||
@@ -85,5 +85,5 @@ class MergeController(QObserver):
|
||||
|
||||
def viz_revision(self):
|
||||
revision = self.model.get_revision()
|
||||
browser = self.model.get_global_ugit_historybrowser()
|
||||
browser = self.model.get_history_browser()
|
||||
utils.fork(browser, revision)
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import os
|
||||
from PyQt4.QtGui import QDialog
|
||||
|
||||
from ugit import utils
|
||||
from ugit import qtutils
|
||||
from ugit.views import PushView
|
||||
from ugit.qobserver import QObserver
|
||||
|
||||
def push_branches(model, parent):
|
||||
model = model.clone()
|
||||
view = PushView(parent)
|
||||
controller = PushController(model, view)
|
||||
view.show()
|
||||
return view.exec_() == QDialog.Accepted
|
||||
|
||||
class PushController(QObserver):
|
||||
def init(self, model, view):
|
||||
self.add_observables(
|
||||
'remote',
|
||||
'remotes',
|
||||
'local_branch',
|
||||
'local_branches',
|
||||
'remote_branch',
|
||||
'remote_branches',
|
||||
)
|
||||
self.add_actions( remotes = self.display_remotes )
|
||||
self.add_callbacks(
|
||||
remotes = self.update_remotes,
|
||||
local_branches = self.update_local_branches,
|
||||
remote_branches = self.update_remote_branches,
|
||||
push_button = self.push_to_remote_branch,
|
||||
)
|
||||
self.refresh_view()
|
||||
|
||||
def display_remotes(self, widget):
|
||||
displayed = []
|
||||
for remote in self.model.get_remotes():
|
||||
url = self.model.remote_url(remote)
|
||||
display = '%s\t(%s %s)' \
|
||||
% (remote, unicode(self.tr('URL:')), url)
|
||||
displayed.append(display)
|
||||
qtutils.set_items(widget,displayed)
|
||||
|
||||
def push_to_remote_branch(self):
|
||||
if not self.model.get_remote():
|
||||
errmsg = self.tr('No repository selected.')
|
||||
qtutils.show_output(errmsg)
|
||||
return
|
||||
|
||||
if not self.model.get_remote_branch():
|
||||
errmsg = self.tr('Please supply a branch name.')
|
||||
qtutils.show_output(errmsg)
|
||||
return
|
||||
|
||||
if not self.model.get_local_branch():
|
||||
msg = self.tr('Pushing with an empty local branch '
|
||||
+ 'will remove the remote branch.\n'
|
||||
+ 'Continue?')
|
||||
if not qtutils.question(self.view, self.tr('warning'), msg):
|
||||
return
|
||||
|
||||
remote = self.model.get_remote()
|
||||
local_branch = self.model.get_local_branch()
|
||||
remote_branch = self.model.get_remote_branch()
|
||||
ffwd = self.view.ffwd_only_checkbox.isChecked()
|
||||
tags = self.view.tags_checkbox.isChecked()
|
||||
|
||||
status, output = self.model.push_helper(
|
||||
remote,
|
||||
local_branch,
|
||||
remote_branch,
|
||||
ffwd=ffwd,
|
||||
tags=tags
|
||||
)
|
||||
qtutils.show_output(output)
|
||||
if not status:
|
||||
self.view.accept()
|
||||
|
||||
def update_remotes(self,*rest):
|
||||
widget = self.view.remotes
|
||||
remotes = self.model.get_remotes()
|
||||
selection = qtutils.get_selected_item(widget,remotes)
|
||||
if not selection: return
|
||||
self.model.set_remote(selection)
|
||||
self.view.remote.selectAll()
|
||||
|
||||
def update_local_branches(self,*rest):
|
||||
branches = self.model.get_local_branches()
|
||||
widget = self.view.local_branches
|
||||
selection = qtutils.get_selected_item(widget,branches)
|
||||
if not selection: return
|
||||
|
||||
self.model.set_local_branch(selection)
|
||||
self.model.set_remote_branch(selection)
|
||||
|
||||
self.view.local_branch.selectAll()
|
||||
self.view.remote_branch.selectAll()
|
||||
|
||||
def update_remote_branches(self,*rest):
|
||||
widget = self.view.remote_branches
|
||||
branches = self.model.get_remote_branches()
|
||||
selection = qtutils.get_selected_item(widget,branches)
|
||||
if not selection: return
|
||||
|
||||
branch = utils.basename(selection)
|
||||
if branch == 'HEAD': return
|
||||
self.model.set_remote_branch(branch)
|
||||
self.view.remote_branch.selectAll()
|
||||
@@ -0,0 +1,138 @@
|
||||
import os
|
||||
from PyQt4.QtGui import QDialog
|
||||
|
||||
from ugit import utils
|
||||
from ugit import qtutils
|
||||
from ugit.views import RemoteView
|
||||
from ugit.qobserver import QObserver
|
||||
|
||||
def remote_action(model, parent, action):
|
||||
model = model.clone()
|
||||
model.create(
|
||||
remotename='',
|
||||
tags_checkbox=False,
|
||||
ffwd_only_checkbox=True,
|
||||
)
|
||||
if action == "Fetch":
|
||||
model.set_tags_checkbox(True)
|
||||
view = RemoteView(parent, action)
|
||||
controller = RemoteController(model, view, action)
|
||||
view.show()
|
||||
return view.exec_() == QDialog.Accepted
|
||||
|
||||
class RemoteController(QObserver):
|
||||
def init(self, model, view, action):
|
||||
self.add_observables(
|
||||
'remotename',
|
||||
'remotes',
|
||||
'local_branch',
|
||||
'local_branches',
|
||||
'remote_branch',
|
||||
'remote_branches',
|
||||
'tags_checkbox',
|
||||
'ffwd_only_checkbox',
|
||||
)
|
||||
|
||||
self.action_method = {
|
||||
"Fetch": self.fetch_action,
|
||||
"Push": self.push_action,
|
||||
"Pull": self.pull_action,
|
||||
}[action]
|
||||
|
||||
self.add_actions( remotes = self.display_remotes )
|
||||
self.add_callbacks(
|
||||
action_button = self.action_method,
|
||||
remotes = self.update_remotes,
|
||||
local_branches = self.update_local_branches,
|
||||
remote_branches = self.update_remote_branches,
|
||||
)
|
||||
self.refresh_view()
|
||||
if self.view.select_first_remote():
|
||||
self.model.set_remotename(self.model.get_remotes()[0])
|
||||
|
||||
def display_remotes(self, widget):
|
||||
displayed = []
|
||||
for remotename in self.model.get_remotes():
|
||||
url = self.model.remote_url(remotename)
|
||||
display = '%s\t(%s %s)' \
|
||||
% (remotename, unicode(self.tr('URL:')), url)
|
||||
displayed.append(display)
|
||||
qtutils.set_items(widget,displayed)
|
||||
|
||||
def update_remotes(self,*rest):
|
||||
widget = self.view.remotes
|
||||
remotes = self.model.get_remotes()
|
||||
selection = qtutils.get_selected_item(widget,remotes)
|
||||
if not selection: return
|
||||
self.model.set_remotename(selection)
|
||||
self.view.remotename.selectAll()
|
||||
|
||||
def update_local_branches(self,*rest):
|
||||
branches = self.model.get_local_branches()
|
||||
widget = self.view.local_branches
|
||||
selection = qtutils.get_selected_item(widget,branches)
|
||||
if not selection: return
|
||||
|
||||
self.model.set_local_branch(selection)
|
||||
self.model.set_remote_branch(selection)
|
||||
|
||||
self.view.local_branch.selectAll()
|
||||
self.view.remote_branch.selectAll()
|
||||
|
||||
def update_remote_branches(self,*rest):
|
||||
widget = self.view.remote_branches
|
||||
branches = self.model.get_remote_branches()
|
||||
selection = qtutils.get_selected_item(widget,branches)
|
||||
if not selection: return
|
||||
|
||||
branch = utils.basename(selection)
|
||||
if branch == 'HEAD': return
|
||||
self.model.set_remote_branch(branch)
|
||||
self.view.remote_branch.selectAll()
|
||||
|
||||
def check_remote(self):
|
||||
if not self.model.get_remotename():
|
||||
errmsg = self.tr('No repository selected.')
|
||||
qtutils.show_output(errmsg)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def get_common_args(self):
|
||||
return (
|
||||
(self.model.get_remotename(),),
|
||||
{
|
||||
"local_branch": self.model.get_local_branch(),
|
||||
"remote_branch": self.model.get_remote_branch(),
|
||||
"ffwd": self.model.get_ffwd_only_checkbox(),
|
||||
"tags": self.model.get_tags_checkbox(),
|
||||
},
|
||||
)
|
||||
|
||||
def show_results(self, status, output):
|
||||
qtutils.show_output(output)
|
||||
if not status:
|
||||
self.view.accept()
|
||||
else:
|
||||
qtutils.raise_logger()
|
||||
|
||||
#+-------------------------------------------------------------
|
||||
#+ Actions
|
||||
def fetch_action(self):
|
||||
if not self.check_remote():
|
||||
return
|
||||
args, kwargs = self.get_common_args()
|
||||
self.show_results(*self.model.fetch_helper(*args, **kwargs))
|
||||
return
|
||||
|
||||
def pull_action(self):
|
||||
if not self.check_remote():
|
||||
return
|
||||
args, kwargs = self.get_common_args()
|
||||
self.show_results(*self.model.pull_helper(*args, **kwargs))
|
||||
|
||||
def push_action(self):
|
||||
if not self.check_remote():
|
||||
return
|
||||
args, kwargs = self.get_common_args()
|
||||
self.show_results(*self.model.push_helper(*args, **kwargs))
|
||||
@@ -132,6 +132,8 @@ class SearchController(QObserver):
|
||||
button_search = self.search_callback,
|
||||
button_browse = self.browse_callback,
|
||||
commit_list = self.display_callback,
|
||||
button_export = self.export_patch,
|
||||
button_cherrypick = self.cherry_pick,
|
||||
# Radio buttons trigger a search
|
||||
radio_revision = self.search_callback,
|
||||
radio_range = self.search_callback,
|
||||
@@ -216,6 +218,24 @@ class SearchController(QObserver):
|
||||
diff = self.model.get_commit_diff(revision)
|
||||
self.view.commit_text.setText(diff)
|
||||
|
||||
def export_patch(self):
|
||||
widget = self.view.commit_list
|
||||
row, selected = qtutils.get_selected_row(widget)
|
||||
if not selected or len(self.results) < row:
|
||||
return
|
||||
revision = self.results[row][0]
|
||||
qtutils.log(self.model.export_patchset(revision, revision),
|
||||
doraise=True, quiet=False)
|
||||
|
||||
def cherry_pick(self):
|
||||
widget = self.view.commit_list
|
||||
row, selected = qtutils.get_selected_row(widget)
|
||||
if not selected or len(self.results) < row:
|
||||
return
|
||||
revision = self.results[row][0]
|
||||
qtutils.log(self.model.cherry_pick(revision),
|
||||
doraise=True, quiet=False)
|
||||
|
||||
def search_commits(model, mode, browse):
|
||||
def get_date(timespec):
|
||||
return '%04d-%02d-%02d' % time.localtime(timespec)[:3]
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import os
|
||||
|
||||
from ugit import utils
|
||||
from ugit import qtutils
|
||||
from ugit.qobserver import QObserver
|
||||
from ugit.views import StashView
|
||||
|
||||
def stash(model, parent):
|
||||
model = model.clone()
|
||||
model.create( stash_list=[], stash_revids=[] )
|
||||
view = StashView(parent)
|
||||
ctl = StashController(model, view)
|
||||
view.show()
|
||||
|
||||
class StashController(QObserver):
|
||||
def init (self, model, view):
|
||||
self.add_observables("stash_list")
|
||||
self.add_callbacks(
|
||||
button_stash_show = self.stash_show,
|
||||
button_stash_apply = self.stash_apply,
|
||||
button_stash_drop = self.stash_drop,
|
||||
button_stash_clear = self.stash_clear,
|
||||
button_stash_save = self.stash_save,
|
||||
)
|
||||
self.update_model()
|
||||
|
||||
def update_model(self):
|
||||
self.model.set_stash_list(self.model.parse_stash_list())
|
||||
self.model.set_stash_revids(self.model.parse_stash_list(revids=True))
|
||||
self.refresh_view()
|
||||
|
||||
def get_selected_stash(self):
|
||||
list_widget = self.view.stash_list
|
||||
stash_list = self.model.get_stash_revids()
|
||||
return qtutils.get_selected_item(list_widget, stash_list)
|
||||
|
||||
def stash_save(self):
|
||||
answer = qtutils.question(self.view,
|
||||
self.tr('Stash Changes?'),
|
||||
self.tr('This will stash your current changes away for later use.\n'
|
||||
'Continue?'))
|
||||
if not answer:
|
||||
return
|
||||
|
||||
stash_name, ok = qtutils.input(self.tr("Enter a name for this stash"))
|
||||
if not ok:
|
||||
return
|
||||
|
||||
while stash_name in self.model.get_stash_list():
|
||||
qtutils.information(
|
||||
self.tr("Oops!"),
|
||||
self.tr("That name already exists. Please enter another name.")
|
||||
)
|
||||
stash_name, ok = qtutils.input(self.tr("Enter a name for this stash"))
|
||||
if not ok:
|
||||
return
|
||||
|
||||
if not stash_name:
|
||||
return
|
||||
|
||||
# Sanitize our input, just in case
|
||||
stash_name = utils.sanitize_input(stash_name)
|
||||
|
||||
qtutils.log(
|
||||
self.model.stash("save", stash_name),
|
||||
quiet=False,
|
||||
doraise=True
|
||||
)
|
||||
self.view.accept()
|
||||
|
||||
def stash_show(self):
|
||||
"""Shows the current stash in the main view."""
|
||||
selection = self.get_selected_stash()
|
||||
if not selection:
|
||||
return
|
||||
diffstat = self.model.stash("show", selection)
|
||||
diff = self.model.stash("show", "-p", selection)
|
||||
self.view.parent_view.display("%s\n\n%s" % (diffstat, diff))
|
||||
|
||||
def stash_apply(self):
|
||||
selection = self.get_selected_stash()
|
||||
if not selection:
|
||||
return
|
||||
qtutils.log(
|
||||
self.model.stash("pop", selection, with_stderr=True),
|
||||
quiet=False,
|
||||
doraise=True
|
||||
)
|
||||
self.view.accept()
|
||||
|
||||
def stash_drop(self):
|
||||
selection = self.get_selected_stash()
|
||||
if not selection:
|
||||
return
|
||||
answer = qtutils.question(self.view,
|
||||
self.tr('Drop Stash?'),
|
||||
self.tr('This will permanently remove the selected stash.\n'
|
||||
'Recovering these changes may not be possible.\n\n'
|
||||
'Continue?'))
|
||||
if not answer:
|
||||
return
|
||||
qtutils.log(
|
||||
self.model.stash("drop", selection),
|
||||
quiet=False,
|
||||
doraise=True
|
||||
)
|
||||
self.update_model()
|
||||
|
||||
def stash_clear(self):
|
||||
answer = qtutils.question(self.view,
|
||||
self.tr('Drop All Stashes?'),
|
||||
self.tr('This will permanently remove ALL stashed changes.\n'
|
||||
'Recovering these changes may not be possible.\n\n'
|
||||
'Continue?'))
|
||||
if not answer:
|
||||
return
|
||||
self.model.stash("clear"),
|
||||
self.update_model()
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import time
|
||||
from PyQt4.QtGui import QDialog
|
||||
from PyQt4.QtGui import QFont
|
||||
@@ -104,12 +105,14 @@ class OptionsController(QObserver):
|
||||
'global_merge_summary',
|
||||
'global_merge_diffstat',
|
||||
'global_merge_verbosity',
|
||||
'global_gui_editor',
|
||||
'global_gui_diffeditor',
|
||||
'global_gui_diffcontext',
|
||||
'global_gui_historybrowser',
|
||||
'global_ugit_fontdiff_size',
|
||||
'global_ugit_fontdiff',
|
||||
'global_ugit_fontui_size',
|
||||
'global_ugit_fontui',
|
||||
'global_ugit_historybrowser',
|
||||
'global_ugit_savewindowsettings',
|
||||
'global_ugit_saveatexit',
|
||||
)
|
||||
|
||||
+5
-6
@@ -10,7 +10,7 @@ from os.path import realpath
|
||||
|
||||
def setup_environment():
|
||||
"""Prepends sys.path with ugit's module path."""
|
||||
# ex: sys.path = [ ..., /usr/share ]
|
||||
# ex: sys.path = [ /usr/share, ... ]
|
||||
# from ugit import utils
|
||||
# implies: utils = /usr/share/ugit/utils.py
|
||||
ugit = realpath(sys.argv[0])
|
||||
@@ -20,11 +20,11 @@ def setup_environment():
|
||||
sys.path.insert(0, share)
|
||||
|
||||
def main():
|
||||
# cmdline
|
||||
parser = optparse.OptionParser(
|
||||
usage='%prog /repo/path-1 ... /repo/path-N*\n\n'
|
||||
+'*the current directory is used when no '
|
||||
+'paths are specified.''')
|
||||
|
||||
parser.add_option('-v', '--version',
|
||||
help='Show ugit version',
|
||||
dest='version',
|
||||
@@ -54,12 +54,11 @@ def main():
|
||||
for repo in [ os.path.realpath(r) for r in args[1:] ]:
|
||||
utils.fork("git", "ugit", repo)
|
||||
|
||||
# load the model right away so that we can bail out if
|
||||
# when no git repo exists
|
||||
# load the model right away so that we can bail out when
|
||||
# no git repo exists
|
||||
model = Model()
|
||||
|
||||
|
||||
# qt
|
||||
# PyQt4
|
||||
try:
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4 import QtGui
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@ class FileSysEvent(ProcessEvent):
|
||||
class GitNotifier(QThread):
|
||||
def __init__(self, receiver, path):
|
||||
QThread.__init__(self)
|
||||
self.git = git.Git()
|
||||
self.receiver = receiver
|
||||
self.path = path
|
||||
self.abort = False
|
||||
@@ -59,7 +60,7 @@ class GitNotifier(QThread):
|
||||
if not added_flag:
|
||||
self.watch_directory(self.path)
|
||||
# Register files/directories known to git
|
||||
for file in git.ls_files().splitlines():
|
||||
for file in self.git.ls_files().splitlines():
|
||||
directory = os.path.dirname(file)
|
||||
self.watch_directory(directory)
|
||||
added_flag = True
|
||||
|
||||
+513
-54
@@ -1,49 +1,70 @@
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import time
|
||||
import subprocess
|
||||
from cStringIO import StringIO
|
||||
|
||||
# GitPython http://gitorious.org/projects/git-python
|
||||
import git
|
||||
|
||||
from ugit import utils
|
||||
from ugit import model
|
||||
|
||||
#+-------------------------------------------------------------------------
|
||||
#+ A regex for matching the output of git(log|rev-list) --pretty=oneline
|
||||
REV_LIST_REGEX = re.compile('([0-9a-f]+)\W(.*)')
|
||||
|
||||
#+-------------------------------------------------------------------------
|
||||
# List of functions available directly through model.command_name()
|
||||
GIT_COMMANDS = """
|
||||
am annotate apply archive archive_recursive
|
||||
bisect blame branch bundle
|
||||
checkout checkout_index cherry cherry_pick citool
|
||||
clean commit config count_objects
|
||||
describe diff
|
||||
fast_export fetch filter_branch format_patch fsck
|
||||
gc get_tar_commit_id grep gui
|
||||
hard_repack imap_send init instaweb
|
||||
log lost_found ls_files ls_remote ls_tree
|
||||
merge mergetool mv name_rev pull push
|
||||
read_tree rebase relink remote repack
|
||||
request_pull reset revert rev_list rm
|
||||
send_email shortlog show show_branch
|
||||
show_ref stash status submodule svn
|
||||
tag var verify_pack whatchanged
|
||||
""".split()
|
||||
|
||||
class Model(model.Model):
|
||||
"""Provides a friendly wrapper for doing commit git operations."""
|
||||
|
||||
def init(self):
|
||||
"""Reads git repository settings and sets severl methods
|
||||
"""Reads git repository settings and sets several methods
|
||||
so that they refer to the git module. This object is
|
||||
encapsulates ugit's interaction with git.
|
||||
The git module itself should know nothing about ugit
|
||||
whatsoever."""
|
||||
encapsulates ugit's interaction with git."""
|
||||
|
||||
# chdir to the root of the git tree.
|
||||
# This keeps paths relative.
|
||||
cdup = git.rev_parse(show_cdup=True)
|
||||
if cdup:
|
||||
if cdup.startswith('fatal:'):
|
||||
# this is not a git repo
|
||||
sys.stderr.write(cdup+"\n")
|
||||
sys.exit(-1)
|
||||
os.chdir(cdup)
|
||||
self.git = git.Git()
|
||||
os.chdir( self.git.git_dir )
|
||||
|
||||
# Read git config
|
||||
self.init_config_data()
|
||||
self.__init_config_data()
|
||||
|
||||
# Import all git commands from git.py
|
||||
for name, cmd in git.commands.iteritems():
|
||||
setattr(self, name, cmd)
|
||||
for cmd in GIT_COMMANDS:
|
||||
setattr(self, cmd, getattr(self.git, cmd))
|
||||
|
||||
self.create(
|
||||
#####################################################
|
||||
# Used in various places
|
||||
currentbranch = '',
|
||||
remotes = [],
|
||||
remote = '',
|
||||
remotename = '',
|
||||
local_branch = '',
|
||||
remote_branch = '',
|
||||
search_text = '',
|
||||
git_version = git.version(),
|
||||
git_version = self.git.version(),
|
||||
|
||||
#####################################################
|
||||
# Used primarily by the main UI
|
||||
@@ -85,8 +106,7 @@ class Model(model.Model):
|
||||
subtree_names = [],
|
||||
)
|
||||
|
||||
|
||||
def init_config_data(self):
|
||||
def __init_config_data(self):
|
||||
"""Reads git config --list and creates parameters
|
||||
for each setting."""
|
||||
# These parameters are saved in .gitconfig,
|
||||
@@ -110,13 +130,15 @@ class Model(model.Model):
|
||||
'ugit_fontui_size':12,
|
||||
'ugit_fontdiff': '',
|
||||
'ugit_fontdiff_size':12,
|
||||
'ugit_historybrowser': 'gitk',
|
||||
'ugit_savewindowsettings': False,
|
||||
'ugit_saveatexit': False,
|
||||
'gui_editor': 'gvim',
|
||||
'gui_diffeditor': 'xxdiff',
|
||||
'gui_historybrowser': 'gitk',
|
||||
}
|
||||
|
||||
local_dict = git.config_dict(local=True)
|
||||
global_dict = git.config_dict(local=False)
|
||||
local_dict = self.config_dict(local=True)
|
||||
global_dict = self.config_dict(local=False)
|
||||
|
||||
for k,v in local_dict.iteritems():
|
||||
self.set_param('local_'+k, v)
|
||||
@@ -150,15 +172,41 @@ class Model(model.Model):
|
||||
if k not in global_dict:
|
||||
self.set_param('global_'+k, v)
|
||||
|
||||
# Allow EDITOR/DIFF_EDITOR environment variable overrides
|
||||
self.global_gui_editor = os.getenv("GUI_EDITOR", self.global_gui_editor)
|
||||
self.global_gui_diffeditor = os.getenv("DIFF_EDITOR", self.global_gui_diffeditor)
|
||||
|
||||
# Load the diff context
|
||||
git.set_diff_context( self.local_gui_diffcontext )
|
||||
self.diff_context = self.local_gui_diffcontext
|
||||
|
||||
def branch_list(self, remote=False):
|
||||
branches = map(lambda x: x.lstrip('* '),
|
||||
self.git.branch(r=remote).splitlines())
|
||||
if remote:
|
||||
remotes = []
|
||||
for branch in branches:
|
||||
if branch.endswith('/HEAD'):
|
||||
continue
|
||||
remotes.append(branch)
|
||||
return remotes
|
||||
return branches
|
||||
|
||||
def get_config_params(self):
|
||||
params = []
|
||||
params.extend(map(lambda x: 'local_' + x,
|
||||
self.__local_and_global_defaults.keys()))
|
||||
params.extend(map(lambda x: 'global_' + x,
|
||||
self.__local_and_global_defaults.keys()))
|
||||
params.extend(map(lambda x: 'global_' + x,
|
||||
self.__global_defaults.keys()))
|
||||
return params
|
||||
|
||||
def save_config_param(self, param):
|
||||
if param not in self.get_config_params():
|
||||
return
|
||||
value = self.get_param(param)
|
||||
if param == 'local_gui_diffcontext':
|
||||
git.DIFF_CONTEXT = value
|
||||
self.diff_context = value
|
||||
if param.startswith('local_'):
|
||||
param = param[len('local_'):]
|
||||
is_local = True
|
||||
@@ -169,7 +217,7 @@ class Model(model.Model):
|
||||
raise Exception("Invalid param '%s' passed to " % param
|
||||
+ "save_config_param()")
|
||||
param = param.replace('_','.') # model -> git
|
||||
return git.config_set(param, value, local=is_local)
|
||||
return self.config_set(param, value, local=is_local)
|
||||
|
||||
def init_browser_data(self):
|
||||
'''This scans over self.(names, sha1s, types) to generate
|
||||
@@ -185,7 +233,7 @@ class Model(model.Model):
|
||||
self.directory_entries = {}
|
||||
|
||||
# Lookup the tree info
|
||||
tree_info = git.parse_ls_tree(self.get_currentbranch())
|
||||
tree_info = self.parse_ls_tree(self.get_currentbranch())
|
||||
|
||||
self.set_types(map( lambda(x): x[1], tree_info ))
|
||||
self.set_sha1s(map( lambda(x): x[2], tree_info ))
|
||||
@@ -230,8 +278,41 @@ class Model(model.Model):
|
||||
self.subtree_sha1s.append(self.sha1s[idx])
|
||||
self.subtree_names.append(name)
|
||||
|
||||
def add_or_remove(self, *to_process):
|
||||
"""Invokes 'git add' to index the filenames in to_process that exist
|
||||
and 'git rm' for those that do not exist."""
|
||||
|
||||
if not to_process:
|
||||
return 'No files to add or remove.'
|
||||
|
||||
to_add = []
|
||||
to_remove = []
|
||||
|
||||
for filename in to_process:
|
||||
if os.path.exists(filename):
|
||||
to_add.append(filename)
|
||||
|
||||
output = self.git.add(verbose=True, *to_add)
|
||||
|
||||
if len(to_add) == len(to_process):
|
||||
# to_process only contained unremoved files --
|
||||
# short-circuit the removal checks
|
||||
return output
|
||||
|
||||
# Process files to remote
|
||||
for filename in to_process:
|
||||
if not os.path.exists(filename):
|
||||
to_remove.append(filename)
|
||||
output + '\n\n' + self.git.rm(*to_remove)
|
||||
|
||||
def get_editor(self):
|
||||
return self.global_gui_editor
|
||||
|
||||
def get_diffeditor(self):
|
||||
return self.global_gui_diffeditor
|
||||
|
||||
def get_history_browser(self):
|
||||
return self.global_ugit_historybrowser
|
||||
return self.global_gui_historybrowser
|
||||
|
||||
def remember_gui_settings(self):
|
||||
return self.global_ugit_savewindowsettings
|
||||
@@ -256,7 +337,7 @@ class Model(model.Model):
|
||||
if not remote: return
|
||||
self.set_param('remote', remote)
|
||||
branches = utils.grep( '%s/\S+$' % remote,
|
||||
git.branch_list(remote=True), squash=False)
|
||||
self.branch_list(remote=True), squash=False)
|
||||
self.set_remote_branches(branches)
|
||||
|
||||
def add_signoff(self,*rest):
|
||||
@@ -272,7 +353,7 @@ class Model(model.Model):
|
||||
self.set_commitmsg(msg + signoff)
|
||||
|
||||
def apply_diff(self, filename):
|
||||
return git.apply(filename, index=True, cached=True)
|
||||
return self.git.apply(filename, index=True, cached=True)
|
||||
|
||||
def load_commitmsg(self, path):
|
||||
file = open(path, 'r')
|
||||
@@ -284,7 +365,7 @@ class Model(model.Model):
|
||||
'''Queries git for the latest commit message and sets it in
|
||||
self.commitmsg.'''
|
||||
commit_msg = []
|
||||
commit_lines = git.show('HEAD').split('\n')
|
||||
commit_lines = self.git.show('HEAD').split('\n')
|
||||
for idx, msg in enumerate(commit_lines):
|
||||
if idx < 4: continue
|
||||
msg = msg.lstrip()
|
||||
@@ -310,7 +391,7 @@ class Model(model.Model):
|
||||
# Read git status items
|
||||
( staged_items,
|
||||
modified_items,
|
||||
untracked_items ) = git.parse_status()
|
||||
untracked_items ) = self.parse_status()
|
||||
|
||||
# Gather items to be committed
|
||||
for staged in staged_items:
|
||||
@@ -327,12 +408,12 @@ class Model(model.Model):
|
||||
if untracked not in self.get_untracked():
|
||||
self.add_untracked(untracked)
|
||||
|
||||
self.set_currentbranch(git.current_branch())
|
||||
self.set_currentbranch(self.current_branch())
|
||||
self.set_unstaged(self.get_modified() + self.get_untracked())
|
||||
self.set_remotes(git.remote().splitlines())
|
||||
self.set_remote_branches(git.branch_list(remote=True))
|
||||
self.set_local_branches(git.branch_list(remote=False))
|
||||
self.set_tags(git.tag().splitlines())
|
||||
self.set_remotes(self.git.remote().splitlines())
|
||||
self.set_remote_branches(self.branch_list(remote=True))
|
||||
self.set_local_branches(self.branch_list(remote=False))
|
||||
self.set_tags(self.git.tag().splitlines())
|
||||
self.set_revision('')
|
||||
self.set_local_branch('')
|
||||
self.set_remote_branch('')
|
||||
@@ -341,21 +422,11 @@ class Model(model.Model):
|
||||
self.notify_observers('staged','unstaged')
|
||||
|
||||
def delete_branch(self, branch):
|
||||
return git.branch(branch, D=True)
|
||||
return self.git.branch(branch, D=True)
|
||||
|
||||
def get_revision_sha1(self, idx):
|
||||
return self.get_revisions()[idx]
|
||||
|
||||
def get_config_params(self):
|
||||
params = []
|
||||
params.extend(map(lambda x: 'local_' + x,
|
||||
self.__local_and_global_defaults.keys()))
|
||||
params.extend(map(lambda x: 'global_' + x,
|
||||
self.__local_and_global_defaults.keys()))
|
||||
params.extend(map(lambda x: 'global_' + x,
|
||||
self.__global_defaults.keys()))
|
||||
return params
|
||||
|
||||
def apply_font_size(self, param, default):
|
||||
old_font = self.get_param(param)
|
||||
if not old_font:
|
||||
@@ -373,7 +444,7 @@ class Model(model.Model):
|
||||
self.set_param(param, new_size)
|
||||
|
||||
def get_commit_diff(self, sha1):
|
||||
commit = git.show(sha1)
|
||||
commit = self.git.show(sha1)
|
||||
first_newline = commit.index('\n')
|
||||
if commit[first_newline+1:].startswith('Merge:'):
|
||||
return (commit
|
||||
@@ -412,9 +483,9 @@ class Model(model.Model):
|
||||
else:
|
||||
status = 'Untracked, not staged'
|
||||
|
||||
file_type = utils.run_cmd('file',filename, b=True)
|
||||
file_type = utils.run_cmd('file', '-b', filename)
|
||||
if 'binary' in file_type or 'data' in file_type:
|
||||
diff = utils.run_cmd('hexdump', filename, C=True)
|
||||
diff = utils.run_cmd('hexdump', '-C', filename)
|
||||
else:
|
||||
if os.path.exists(filename):
|
||||
file = open(filename, 'r')
|
||||
@@ -425,23 +496,411 @@ class Model(model.Model):
|
||||
return diff, status
|
||||
|
||||
def stage_modified(self):
|
||||
output = git.add(self.get_modified())
|
||||
output = self.git.add(self.get_modified())
|
||||
self.update_status()
|
||||
return output
|
||||
|
||||
def stage_untracked(self):
|
||||
output = git.add(self.get_untracked())
|
||||
output = self.git.add(self.get_untracked())
|
||||
self.update_status()
|
||||
return output
|
||||
|
||||
def reset(self, *items):
|
||||
output = git.reset('--', *items)
|
||||
output = self.git.reset('--', *items)
|
||||
self.update_status()
|
||||
return output
|
||||
|
||||
def unstage_all(self):
|
||||
git.reset('--', *self.get_staged())
|
||||
self.git.reset('--', *self.get_staged())
|
||||
self.update_status()
|
||||
|
||||
def save_gui_settings(self):
|
||||
git.config_set('ugit.geometry', utils.get_geom(), local=False)
|
||||
self.config_set('ugit.geometry', utils.get_geom(), local=False)
|
||||
|
||||
def config_set(self, key=None, value=None, local=True):
|
||||
if key and value is not None:
|
||||
# git config category.key value
|
||||
strval = str(value)
|
||||
if type(value) is bool:
|
||||
# git uses "true" and "false"
|
||||
strval = strval.lower()
|
||||
if local:
|
||||
argv = [ key, strval ]
|
||||
else:
|
||||
argv = [ '--global', key, strval ]
|
||||
return self.git.config(*argv)
|
||||
else:
|
||||
msg = "oops in config_set(key=%s,value=%s,local=%s"
|
||||
raise Exception(msg % (key, value, local))
|
||||
|
||||
def config_dict(self, local=True):
|
||||
"""parses the lines from git config --list into a dictionary"""
|
||||
|
||||
kwargs = {
|
||||
'list': True,
|
||||
'global': not local,
|
||||
}
|
||||
config_lines = self.git.config(**kwargs).splitlines()
|
||||
newdict = {}
|
||||
for line in config_lines:
|
||||
k, v = line.split('=', 1)
|
||||
k = k.replace('.','_') # git -> model
|
||||
if v == 'true' or v == 'false':
|
||||
v = bool(eval(v.title()))
|
||||
try:
|
||||
v = int(eval(v))
|
||||
except:
|
||||
pass
|
||||
newdict[k]=v
|
||||
return newdict
|
||||
|
||||
def commit_with_msg(self, msg, amend=False):
|
||||
"""Creates a git commit."""
|
||||
|
||||
if not msg.endswith('\n'):
|
||||
msg += '\n'
|
||||
# Sure, this is a potential "security risk," but if someone
|
||||
# is trying to intercept/re-write commit messages on your system,
|
||||
# then you probably have bigger problems to worry about.
|
||||
tmpfile = self.get_tmp_filename()
|
||||
|
||||
# Create the commit message file
|
||||
file = open(tmpfile, 'w')
|
||||
file.write(msg)
|
||||
file.close()
|
||||
|
||||
# Run 'git commit'
|
||||
output = self.git.commit(F=tmpfile, amend=amend)
|
||||
os.unlink(tmpfile)
|
||||
|
||||
return ('git commit -F %s --amend %s\n\n%s'
|
||||
% ( tmpfile, amend, output ))
|
||||
|
||||
|
||||
def diffindex(self):
|
||||
return self.git.diff(
|
||||
unified=self.diff_context,
|
||||
stat=True,
|
||||
cached=True
|
||||
)
|
||||
|
||||
def get_tmp_filename(self):
|
||||
# Allow TMPDIR/TMP with a fallback to /tmp
|
||||
env = os.environ
|
||||
basename = '.git.%s.%s' % ( os.getpid(), time.time() )
|
||||
tmpdir = env.get('TMP', env.get('TMPDIR', '/tmp'))
|
||||
return os.path.join( tmpdir, basename )
|
||||
|
||||
def log_helper(self, all=False):
|
||||
"""Returns a pair of parallel arrays listing the revision sha1's
|
||||
and commit summaries."""
|
||||
revs = []
|
||||
summaries = []
|
||||
regex = REV_LIST_REGEX
|
||||
output = self.git.log(pretty='oneline', all=all)
|
||||
for line in output.splitlines():
|
||||
match = regex.match(line)
|
||||
if match:
|
||||
revs.append(match.group(1))
|
||||
summaries.append(match.group(2))
|
||||
return( revs, summaries )
|
||||
|
||||
def parse_rev_list(self, raw_revs):
|
||||
revs = []
|
||||
for line in raw_revs.splitlines():
|
||||
match = REV_LIST_REGEX.match(line)
|
||||
if match:
|
||||
rev_id = match.group(1)
|
||||
summary = match.group(2)
|
||||
revs.append((rev_id, summary,) )
|
||||
return revs
|
||||
|
||||
def rev_list_range(self, start, end):
|
||||
range = '%s..%s' % ( start, end )
|
||||
raw_revs = self.git.rev_list(range, pretty='oneline')
|
||||
return self.parse_rev_list(raw_revs)
|
||||
|
||||
def diff_helper(self,
|
||||
commit=None,
|
||||
filename=None,
|
||||
color=False,
|
||||
cached=True,
|
||||
with_diff_header=False,
|
||||
suppress_header=True,
|
||||
reverse=False):
|
||||
"Invokes git diff on a filepath."
|
||||
|
||||
argv = []
|
||||
if commit:
|
||||
argv.append('%s^..%s' % (commit, commit))
|
||||
|
||||
if filename:
|
||||
argv.append('--')
|
||||
if type(filename) is list:
|
||||
argv.extend(filename)
|
||||
else:
|
||||
argv.append(filename)
|
||||
|
||||
diff = self.git.diff(
|
||||
R=reverse,
|
||||
color=color,
|
||||
cached=cached,
|
||||
patch_with_raw=True,
|
||||
unified=self.diff_context,
|
||||
*argv
|
||||
).splitlines()
|
||||
|
||||
output = StringIO()
|
||||
start = False
|
||||
del_tag = 'deleted file mode '
|
||||
|
||||
headers = []
|
||||
deleted = cached and not os.path.exists(filename)
|
||||
for line in diff:
|
||||
if not start and '@@ ' in line and ' @@' in line:
|
||||
start = True
|
||||
if start or(deleted and del_tag in line):
|
||||
output.write(line + '\n')
|
||||
else:
|
||||
if with_diff_header:
|
||||
headers.append(line)
|
||||
elif not suppress_header:
|
||||
output.write(line + '\n')
|
||||
result = output.getvalue()
|
||||
output.close()
|
||||
if with_diff_header:
|
||||
return('\n'.join(headers), result)
|
||||
else:
|
||||
return result
|
||||
|
||||
def git_repo_path(self, *subpaths):
|
||||
paths = [ self.git.rev_parse(git_dir=True) ]
|
||||
paths.extend(subpaths)
|
||||
return os.path.realpath(os.path.join(*paths))
|
||||
|
||||
def get_merge_message_path(self):
|
||||
for file in ('MERGE_MSG', 'SQUASH_MSG'):
|
||||
path = self.git_repo_path(file)
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
return None
|
||||
|
||||
def get_merge_message(self):
|
||||
return self.git.fmt_merge_msg(
|
||||
'--file', self.git_repo_path('FETCH_HEAD')
|
||||
)
|
||||
|
||||
def abort_merge(self):
|
||||
# Reset the worktree
|
||||
output = self.git.read_tree("HEAD", reset=True, u=True, v=True)
|
||||
# remove MERGE_HEAD
|
||||
merge_head = self.git_repo_path('MERGE_HEAD')
|
||||
if os.path.exists(merge_head):
|
||||
os.unlink(merge_head)
|
||||
# remove MERGE_MESSAGE, etc.
|
||||
merge_msg_path = self.get_merge_message_path()
|
||||
while merge_msg_path:
|
||||
os.unlink(merge_msg_path)
|
||||
merge_msg_path = self.get_merge_message_path()
|
||||
|
||||
|
||||
def parse_status(self):
|
||||
"""RETURNS: A tuple of staged, unstaged and untracked file lists.
|
||||
"""
|
||||
def eval_path(path):
|
||||
"""handles quoted paths."""
|
||||
if path.startswith('"') and path.endswith('"'):
|
||||
return eval(path)
|
||||
else:
|
||||
return path
|
||||
|
||||
MODIFIED_TAG = '# Changed but not updated:'
|
||||
UNTRACKED_TAG = '# Untracked files:'
|
||||
RGX_RENAMED = re.compile(
|
||||
'(#\trenamed:\s+)'
|
||||
'(.*?)\s->\s(.*)'
|
||||
)
|
||||
RGX_MODIFIED = re.compile(
|
||||
'(#\tmodified:\s+'
|
||||
'|#\tnew file:\s+'
|
||||
'|#\tdeleted:\s+)'
|
||||
)
|
||||
staged = []
|
||||
unstaged = []
|
||||
untracked = []
|
||||
|
||||
STAGED_MODE = 0
|
||||
UNSTAGED_MODE = 1
|
||||
UNTRACKED_MODE = 2
|
||||
|
||||
current_dest = staged
|
||||
mode = STAGED_MODE
|
||||
|
||||
for status_line in self.git.status().splitlines():
|
||||
if status_line == MODIFIED_TAG:
|
||||
mode = UNSTAGED_MODE
|
||||
current_dest = unstaged
|
||||
continue
|
||||
elif status_line == UNTRACKED_TAG:
|
||||
mode = UNTRACKED_MODE
|
||||
current_dest = untracked
|
||||
continue
|
||||
# Staged/unstaged modified/renamed/deleted files
|
||||
if mode is STAGED_MODE or mode is UNSTAGED_MODE:
|
||||
match = RGX_MODIFIED.match(status_line)
|
||||
if match:
|
||||
tag = match.group(0)
|
||||
filename = status_line.replace(tag, '')
|
||||
current_dest.append(eval_path(filename))
|
||||
continue
|
||||
match = RGX_RENAMED.match(status_line)
|
||||
if match:
|
||||
oldname = match.group(2)
|
||||
newname = match.group(3)
|
||||
current_dest.append(eval_path(oldname))
|
||||
current_dest.append(eval_path(newname))
|
||||
continue
|
||||
# Untracked files
|
||||
elif mode is UNTRACKED_MODE:
|
||||
if status_line.startswith('#\t'):
|
||||
current_dest.append(eval_path(status_line[2:]))
|
||||
|
||||
return( staged, unstaged, untracked )
|
||||
|
||||
def reset_helper(self, *args, **kwargs):
|
||||
return self.git.reset('--', *args, **kwargs)
|
||||
|
||||
def remote_url(self, name):
|
||||
return self.git.config('remote.%s.url' % name, get=True)
|
||||
|
||||
def get_remote_args(self, remote,
|
||||
local_branch='', remote_branch='',
|
||||
ffwd=True, tags=False):
|
||||
if ffwd:
|
||||
branch_arg = '%s:%s' % ( remote_branch, local_branch )
|
||||
else:
|
||||
branch_arg = '+%s:%s' % ( remote_branch, local_branch )
|
||||
args = [remote]
|
||||
if local_branch and remote_branch:
|
||||
args.append(branch_arg)
|
||||
kwargs = { "with_stderr": True, "with_status": True, "tags": tags }
|
||||
return (args, kwargs)
|
||||
|
||||
def fetch_helper(self, *args, **kwargs):
|
||||
"""
|
||||
Fetches remote_branch to local_branch only if
|
||||
remote_branch and local_branch are both supplied.
|
||||
If either is ommitted, "git fetch <remote>" is performed instead.
|
||||
Returns (status,output)
|
||||
"""
|
||||
args, kwargs = self.get_remote_args(*args, **kwargs)
|
||||
return self.git.fetch(v=True, *args, **kwargs)
|
||||
|
||||
def push_helper(self, *args, **kwargs):
|
||||
"""
|
||||
Pushes local_branch to remote's remote_branch only if
|
||||
remote_branch and local_branch both are supplied.
|
||||
If either is ommitted, "git push <remote>" is performed instead.
|
||||
Returns (status,output)
|
||||
"""
|
||||
args, kwargs = self.get_remote_args(*args, **kwargs)
|
||||
return self.git.push(*args, **kwargs)
|
||||
|
||||
def pull_helper(self, *args, **kwargs):
|
||||
"""
|
||||
Pushes branches. If local_branch or remote_branch is ommitted,
|
||||
"git pull <remote>" is performed instead of
|
||||
"git pull <remote> <remote_branch>:<local_branch>
|
||||
Returns (status,output)
|
||||
"""
|
||||
args, kwargs = self.get_remote_args(*args, **kwargs)
|
||||
return self.git.pull(v=True, *args, **kwargs)
|
||||
|
||||
|
||||
def parse_ls_tree(self, rev):
|
||||
"""Returns a list of(mode, type, sha1, path) tuples."""
|
||||
lines = self.git.ls_tree(rev, r=True).splitlines()
|
||||
output = []
|
||||
regex = re.compile('^(\d+)\W(\w+)\W(\w+)[ \t]+(.*)$')
|
||||
for line in lines:
|
||||
match = regex.match(line)
|
||||
if match:
|
||||
mode = match.group(1)
|
||||
objtype = match.group(2)
|
||||
sha1 = match.group(3)
|
||||
filename = match.group(4)
|
||||
output.append((mode, objtype, sha1, filename,) )
|
||||
return output
|
||||
|
||||
def format_patch_helper(self, to_export, revs, output='patches'):
|
||||
"""writes patches named by to_export to the output directory."""
|
||||
|
||||
outlines = []
|
||||
|
||||
cur_rev = to_export[0]
|
||||
cur_master_idx = revs.index(cur_rev)
|
||||
|
||||
patches_to_export = [ [cur_rev] ]
|
||||
patchset_idx = 0
|
||||
|
||||
# Group the patches into continuous sets
|
||||
for idx, rev in enumerate(to_export[1:]):
|
||||
# Limit the search to the current neighborhood for efficiency
|
||||
master_idx = revs[ cur_master_idx: ].index(rev)
|
||||
master_idx += cur_master_idx
|
||||
if master_idx == cur_master_idx + 1:
|
||||
patches_to_export[ patchset_idx ].append(rev)
|
||||
cur_master_idx += 1
|
||||
continue
|
||||
else:
|
||||
patches_to_export.append([ rev ])
|
||||
cur_master_idx = master_idx
|
||||
patchset_idx += 1
|
||||
|
||||
# Export each patchsets
|
||||
for patchset in patches_to_export:
|
||||
cmdoutput = self.export_patchset(
|
||||
patchset[0],
|
||||
patchset[-1],
|
||||
output="patches",
|
||||
n=len(patchset) > 1,
|
||||
thread=True,
|
||||
patch_with_stat=True,
|
||||
)
|
||||
outlines.append(cmdoutput)
|
||||
return '\n'.join(outlines)
|
||||
|
||||
def export_patchset(self, start, end, output="patches", **kwargs):
|
||||
revarg = '%s^..%s' % (start, end)
|
||||
return self.git.format_patch("-o", output, revarg, **kwargs)
|
||||
|
||||
def current_branch(self):
|
||||
"""Parses 'git branch' to find the current branch."""
|
||||
branches = self.git.branch().splitlines()
|
||||
for branch in branches:
|
||||
if branch.startswith('* '):
|
||||
return branch.lstrip('* ')
|
||||
return 'Detached HEAD'
|
||||
|
||||
def create_branch(self, name, base, track=False):
|
||||
"""Creates a branch starting from base. Pass track=True
|
||||
to create a remote tracking branch."""
|
||||
return self.git.branch(name, base, track=track)
|
||||
|
||||
def cherry_pick_list(self, revs, **kwargs):
|
||||
"""Cherry-picks each revision into the current branch.
|
||||
Returns a list of command output strings (1 per cherry pick)"""
|
||||
if not revs:
|
||||
return []
|
||||
cherries = []
|
||||
for rev in revs:
|
||||
cherries.append(self.git.cherry_pick(rev, **kwargs))
|
||||
return '\n'.join(cherries)
|
||||
|
||||
def parse_stash_list(self, revids=False):
|
||||
"""Parses "git stash list" and returns a list of stashes."""
|
||||
stashes = self.stash("list").splitlines()
|
||||
if revids:
|
||||
return [ s[:s.index(':')] for s in stashes ]
|
||||
else:
|
||||
return [ s[s.index(':')+1:] for s in stashes ]
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@ from ugit.observer import Observer
|
||||
|
||||
class QObserver(Observer, QObject):
|
||||
|
||||
def __init__(self, model, view):
|
||||
def __init__(self, model, view, *args, **kwargs):
|
||||
Observer.__init__(self, model)
|
||||
QObject.__init__(self)
|
||||
|
||||
@@ -31,9 +31,9 @@ class QObserver(Observer, QObject):
|
||||
self.__view_to_model = {}
|
||||
self.__connected = []
|
||||
# Call user-defined startup routine
|
||||
self.init(model, view)
|
||||
self.init(model, view, *args, **kwargs)
|
||||
|
||||
def init(self, model, view):
|
||||
def init(self, model, view, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def SLOT(self, *args):
|
||||
|
||||
+4
-1
@@ -17,6 +17,9 @@ def log(output, quiet=True, doraise=False):
|
||||
if quiet: return
|
||||
LOGGER.show()
|
||||
if not doraise: return
|
||||
raise_logger()
|
||||
|
||||
def raise_logger():
|
||||
LOGGER.raise_()
|
||||
|
||||
def input(msg, title=None):
|
||||
@@ -30,7 +33,7 @@ def close_log_window():
|
||||
LOGGER.hide()
|
||||
LOGGER.done(0)
|
||||
|
||||
def show_output(output):
|
||||
def show_output(output, **kwargs):
|
||||
if not output: return
|
||||
log(output, quiet=False)
|
||||
|
||||
|
||||
+30
-8
@@ -6,8 +6,6 @@ import subprocess
|
||||
from cStringIO import StringIO
|
||||
|
||||
import defaults
|
||||
from git import run_cmd
|
||||
from git import get_tmp_filename
|
||||
|
||||
PREFIX = os.path.realpath(os.path.dirname(os.path.dirname(sys.argv[0])))
|
||||
QMDIR = os.path.join(PREFIX, 'share', 'ugit', 'qm')
|
||||
@@ -25,6 +23,23 @@ KNOWN_FILE_TYPES = {
|
||||
'image': 'image.png',
|
||||
}
|
||||
|
||||
def run_cmd(*command):
|
||||
"""Runs a *command argument list and returns the output.
|
||||
e.g. run_cmd("echo", "hello", "world")
|
||||
"""
|
||||
# Start the process
|
||||
proc = subprocess.Popen(command, stdout = subprocess.PIPE)
|
||||
|
||||
# Wait for the process to return
|
||||
stdout_value = proc.stdout.read()
|
||||
proc.stdout.close()
|
||||
proc.wait()
|
||||
status = proc.poll()
|
||||
|
||||
# Strip off trailing whitespace by default
|
||||
return stdout_value.rstrip()
|
||||
|
||||
|
||||
def get_qm_for_locale(locale):
|
||||
regex = re.compile(r'([^\.])+\..*$')
|
||||
match = regex.match(locale)
|
||||
@@ -201,9 +216,11 @@ def write(path, contents):
|
||||
file.close()
|
||||
|
||||
class DiffParser(object):
|
||||
def __init__(self, model,
|
||||
filename='', cached=True):
|
||||
self.__header_pattern = re.compile('^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*')
|
||||
def __init__( self, model,
|
||||
filename='',
|
||||
cached=True
|
||||
):
|
||||
self.__header_re = re.compile('^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*')
|
||||
self.__headers = []
|
||||
|
||||
self.__idx = -1
|
||||
@@ -350,7 +367,7 @@ class DiffParser(object):
|
||||
self.__headers = []
|
||||
|
||||
for idx, line in enumerate(diff.splitlines()):
|
||||
match = self.__header_pattern.match(line)
|
||||
match = self.__header_re.match(line)
|
||||
if match:
|
||||
self.__headers.append([
|
||||
int(match.group(1)),
|
||||
@@ -390,14 +407,19 @@ class DiffParser(object):
|
||||
for idx in self.selected:
|
||||
contents = self.get_diff_subset(idx, start, end)
|
||||
if contents:
|
||||
tmpfile = get_tmp_filename()
|
||||
tmpfile = self.model.get_tmp_filename()
|
||||
write(tmpfile, contents)
|
||||
self.model.apply_diff(tmpfile)
|
||||
os.unlink(tmpfile)
|
||||
# Process a complete hunk
|
||||
else:
|
||||
for idx, diff in enumerate(self.diffs):
|
||||
tmpfile = get_tmp_filename()
|
||||
tmpfile = self.model.get_tmp_filename()
|
||||
if self.write_diff(tmpfile,idx):
|
||||
self.model.apply_diff(tmpfile)
|
||||
os.unlink(tmpfile)
|
||||
|
||||
def sanitize_input(input):
|
||||
for c in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
|
||||
input = input.replace(c, '_')
|
||||
return input
|
||||
|
||||
+60
-62
@@ -11,7 +11,7 @@ from ugit.syntax import DiffSyntaxHighlighter
|
||||
from ugit.syntax import LogSyntaxHighlighter
|
||||
|
||||
from main import Ui_main
|
||||
from push import Ui_push
|
||||
from remote import Ui_remote
|
||||
from branch import Ui_branch
|
||||
from commit import Ui_commit
|
||||
from logger import Ui_logger
|
||||
@@ -20,13 +20,25 @@ from options import Ui_options
|
||||
from createbranch import Ui_createbranch
|
||||
from merge import Ui_merge
|
||||
from bookmark import Ui_bookmark
|
||||
from stash import Ui_stash
|
||||
|
||||
class View(Ui_main, QMainWindow):
|
||||
def CreateStandardView(uiclass, qtclass):
|
||||
"""CreateStandardView returns a class closure of uiclass and qtclass.
|
||||
This class performs the standard setup common to all view classes."""
|
||||
class StandardView(uiclass, qtclass):
|
||||
def __init__(self, parent=None, *args, **kwargs):
|
||||
qtclass.__init__(self, parent)
|
||||
uiclass.__init__(self)
|
||||
self.parent_view = parent
|
||||
self.setupUi(self)
|
||||
self.init(parent, *args, **kwargs)
|
||||
def init(self, parent, *args, **kwargs):
|
||||
pass
|
||||
return StandardView
|
||||
|
||||
class View(CreateStandardView(Ui_main, QMainWindow)):
|
||||
'''The main ugit interface.'''
|
||||
def __init__(self, parent=None):
|
||||
QMainWindow.__init__(self, parent)
|
||||
Ui_main.__init__(self)
|
||||
self.setupUi(self)
|
||||
def init(self, parent=None):
|
||||
self.staged.setAlternatingRowColors(True)
|
||||
self.unstaged.setAlternatingRowColors(True)
|
||||
self.set_display = self.display_text.setText
|
||||
@@ -92,17 +104,17 @@ class View(Ui_main, QMainWindow):
|
||||
selection = cursor.selection().toPlainText()
|
||||
num_selected_lines = selection.count('\n')
|
||||
return offset, selection
|
||||
def display(self, text):
|
||||
self.set_display(text)
|
||||
self.diff_dock.raise_()
|
||||
|
||||
class LogView(Ui_logger, QDialog):
|
||||
class LogView(CreateStandardView(Ui_logger, QDialog)):
|
||||
'''A simple dialog to display command logs.'''
|
||||
def __init__(self, parent=None, output=None):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_logger.__init__(self)
|
||||
self.setupUi(self)
|
||||
def init(self, parent=None, output=None):
|
||||
self.setWindowTitle(self.tr('Git Command Log'))
|
||||
# Syntax highlight the log window
|
||||
LogSyntaxHighlighter(self.output_text.document())
|
||||
if output: self.set_output(output)
|
||||
if output:
|
||||
self.set_output(output)
|
||||
def clear(self):
|
||||
self.output_text.clear()
|
||||
def set_output(self, output):
|
||||
@@ -119,14 +131,12 @@ class LogView(Ui_logger, QDialog):
|
||||
cursor.movePosition(cursor.End)
|
||||
text.setTextCursor(cursor)
|
||||
|
||||
class BranchView(Ui_branch, QDialog):
|
||||
class BranchView(CreateStandardView(Ui_branch, QDialog)):
|
||||
'''A dialog for choosing branches.'''
|
||||
def __init__(self, parent=None, branches=None):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_branch.__init__(self)
|
||||
self.setupUi(self)
|
||||
def init(self, parent, branches):
|
||||
self.reset()
|
||||
if branches: self.add(branches)
|
||||
if branches:
|
||||
self.add(branches)
|
||||
def reset(self):
|
||||
self.branches = []
|
||||
self.branch_combo.clear()
|
||||
@@ -141,53 +151,41 @@ class BranchView(Ui_branch, QDialog):
|
||||
else:
|
||||
return None
|
||||
|
||||
class CommitView(Ui_commit, QDialog):
|
||||
def __init__(self, parent=None, title=None):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_commit.__init__(self)
|
||||
self.setupUi(self)
|
||||
if title: self.setWindowTitle(title)
|
||||
# Make the list widget slighty larger
|
||||
self.splitter.setSizes([ 50, 200 ])
|
||||
DiffSyntaxHighlighter(self.commit_text.document(),
|
||||
whitespace=False)
|
||||
class CommitView(CreateStandardView(Ui_commit, QDialog)):
|
||||
def init(self, parent=None, title=None):
|
||||
if title: self.setWindowTitle(title)
|
||||
# Make the list widget slighty larger
|
||||
self.splitter.setSizes([ 50, 200 ])
|
||||
DiffSyntaxHighlighter(self.commit_text.document(),
|
||||
whitespace=False)
|
||||
|
||||
class CreateBranchView(Ui_createbranch, QDialog):
|
||||
def __init__(self, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_createbranch.__init__(self)
|
||||
self.setupUi(self)
|
||||
|
||||
class PushView(Ui_push, QDialog):
|
||||
def __init__(self, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_push.__init__(self)
|
||||
self.setupUi(self)
|
||||
|
||||
class OptionsView(Ui_options, QDialog):
|
||||
def __init__(self, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_options.__init__(self)
|
||||
self.setupUi(self)
|
||||
|
||||
class SearchView(Ui_search, QDialog):
|
||||
def __init__(self, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_search.__init__(self)
|
||||
self.setupUi(self)
|
||||
class SearchView(CreateStandardView(Ui_search, QDialog)):
|
||||
def init(self, parent=None):
|
||||
self.input.setFocus()
|
||||
DiffSyntaxHighlighter(self.commit_text.document(),
|
||||
whitespace=False)
|
||||
|
||||
class MergeView(Ui_merge, QDialog):
|
||||
def __init__(self, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_merge.__init__(self)
|
||||
self.setupUi(self)
|
||||
class MergeView(CreateStandardView(Ui_merge, QDialog)):
|
||||
def init(self, parent=None):
|
||||
self.revision.setFocus()
|
||||
|
||||
class BookmarkView(Ui_bookmark, QDialog):
|
||||
def __init__(self, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_bookmark.__init__(self)
|
||||
self.setupUi(self)
|
||||
class RemoteView(CreateStandardView(Ui_remote, QDialog)):
|
||||
def init(self, parent=None, button_text=''):
|
||||
if button_text:
|
||||
self.action_button.setText(button_text)
|
||||
self.setWindowTitle(button_text)
|
||||
def select_first_remote(self):
|
||||
item = self.remotes.item(0)
|
||||
if item:
|
||||
self.remotes.setItemSelected(item, True)
|
||||
self.remotes.setCurrentItem(item)
|
||||
self.remotename.setText(item.text())
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# These are views that do not contain any custom methods
|
||||
CreateBranchView = CreateStandardView(Ui_createbranch, QDialog)
|
||||
OptionsView = CreateStandardView(Ui_options, QDialog)
|
||||
BookmarkView = CreateStandardView(Ui_bookmark, QDialog)
|
||||
StashView = CreateStandardView(Ui_stash, QDialog)
|
||||
|
||||
+42
-13
@@ -76,6 +76,7 @@
|
||||
<addaction name="menu_unstage_selected" />
|
||||
<addaction name="separator" />
|
||||
<addaction name="menu_commit" />
|
||||
<addaction name="menu_stash" />
|
||||
<addaction name="separator" />
|
||||
<addaction name="menu_export_patches" />
|
||||
<addaction name="menu_cherry_pick" />
|
||||
@@ -660,6 +661,29 @@ otherwise only the selected items will be committed.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType" >
|
||||
<enum>QSizePolicy::Minimum</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0" >
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="fetch_button" >
|
||||
<property name="text" >
|
||||
<string>Fetch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="push_button" >
|
||||
<property name="text" >
|
||||
@@ -668,7 +692,14 @@ otherwise only the selected items will be committed.</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<widget class="QPushButton" name="pull_button" >
|
||||
<property name="text" >
|
||||
<string>Pull</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
@@ -677,8 +708,8 @@ otherwise only the selected items will be committed.</string>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>1</height>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
@@ -1032,11 +1063,6 @@ retrieve the latest commit message prior to committing.</string>
|
||||
<string>Delete...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_open" >
|
||||
<property name="text" >
|
||||
<string>Open...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_browse_commits" >
|
||||
<property name="text" >
|
||||
<string>Browse Commits</string>
|
||||
@@ -1057,11 +1083,6 @@ retrieve the latest commit message prior to committing.</string>
|
||||
<string>By Revision Range...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_search_date" >
|
||||
<property name="text" >
|
||||
<string>Commits Since Date...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_search_date_range" >
|
||||
<property name="text" >
|
||||
<string>Commits within Date Range...</string>
|
||||
@@ -1117,6 +1138,14 @@ retrieve the latest commit message prior to committing.</string>
|
||||
<string>Open Repository...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="menu_stash" >
|
||||
<property name="text" >
|
||||
<string>Stash...</string>
|
||||
</property>
|
||||
<property name="shortcut" >
|
||||
<string>Alt+Shift+S</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="2" margin="2" />
|
||||
<tabstops>
|
||||
|
||||
+320
-270
@@ -8,286 +8,336 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>613</width>
|
||||
<height>342</height>
|
||||
<width>654</width>
|
||||
<height>345</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Options</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<layout class="QGridLayout" name="gridLayout_3" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label_2" >
|
||||
<property name="text" >
|
||||
<string>Main Font</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2" >
|
||||
<widget class="QSpinBox" name="global_ugit_fontui_size" >
|
||||
<property name="minimum" >
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>12</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3" colspan="4" >
|
||||
<widget class="QFontComboBox" name="global_ugit_fontui" >
|
||||
<property name="currentFont" >
|
||||
<font>
|
||||
<family>Helvetica</family>
|
||||
</font>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QGridLayout" name="gridLayout_2" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label_2" >
|
||||
<property name="text" >
|
||||
<string>Main Font</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" >
|
||||
<widget class="QSpinBox" name="global_ugit_fontui_size" >
|
||||
<property name="minimum" >
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>12</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2" colspan="3" >
|
||||
<widget class="QFontComboBox" name="global_ugit_fontui" >
|
||||
<property name="currentFont" >
|
||||
<font>
|
||||
<family>Helvetica</family>
|
||||
</font>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_3" >
|
||||
<property name="text" >
|
||||
<string>Diff/Console Font</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<widget class="QSpinBox" name="global_ugit_fontdiff_size" >
|
||||
<property name="minimum" >
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>12</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" colspan="3" >
|
||||
<widget class="QFontComboBox" name="global_ugit_fontdiff" >
|
||||
<property name="fontFilters" >
|
||||
<set>QFontComboBox::MonospacedFonts</set>
|
||||
</property>
|
||||
<property name="currentFont" >
|
||||
<font>
|
||||
<family>Bitstream Vera Sans Mono</family>
|
||||
</font>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="label_13" >
|
||||
<property name="text" >
|
||||
<string>Editor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QLineEdit" name="global_gui_editor" >
|
||||
<property name="text" >
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" >
|
||||
<widget class="QLabel" name="label_11" >
|
||||
<property name="text" >
|
||||
<string>History browser</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3" colspan="2" >
|
||||
<widget class="QLineEdit" name="global_gui_historybrowser" >
|
||||
<property name="text" >
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" >
|
||||
<widget class="QLabel" name="label_12" >
|
||||
<property name="text" >
|
||||
<string>Diff Editor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" >
|
||||
<widget class="QLineEdit" name="global_gui_diffeditor" >
|
||||
<property name="text" >
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2" colspan="2" >
|
||||
<widget class="QCheckBox" name="global_ugit_savewindowsettings" >
|
||||
<property name="text" >
|
||||
<string>Remember Window Settings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4" >
|
||||
<widget class="QCheckBox" name="global_ugit_saveatexit" >
|
||||
<property name="text" >
|
||||
<string>Save Window Settings At Exit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_3" >
|
||||
<property name="text" >
|
||||
<string>Diff/Console Font</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2" >
|
||||
<widget class="QSpinBox" name="global_ugit_fontdiff_size" >
|
||||
<property name="minimum" >
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>12</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3" colspan="4" >
|
||||
<widget class="QFontComboBox" name="global_ugit_fontdiff" >
|
||||
<property name="fontFilters" >
|
||||
<set>QFontComboBox::MonospacedFonts</set>
|
||||
</property>
|
||||
<property name="currentFont" >
|
||||
<font>
|
||||
<family>Andale Mono</family>
|
||||
</font>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8" >
|
||||
<item>
|
||||
<widget class="QGroupBox" name="local_groupbox" >
|
||||
<property name="title" >
|
||||
<string>Local Repository</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" >
|
||||
<item row="0" column="1" colspan="2" >
|
||||
<widget class="QLineEdit" name="local_user_name" />
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_5" >
|
||||
<property name="text" >
|
||||
<string>Email Address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2" >
|
||||
<widget class="QLineEdit" name="local_user_email" />
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2" >
|
||||
<widget class="QCheckBox" name="local_merge_summary" >
|
||||
<property name="text" >
|
||||
<string>Summarize Merge Commits</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="3" >
|
||||
<widget class="QCheckBox" name="local_merge_diffstat" >
|
||||
<property name="text" >
|
||||
<string>Show Diffstat After Merge</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label_6" >
|
||||
<property name="text" >
|
||||
<string>Merge Verbosity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2" >
|
||||
<widget class="QSpinBox" name="local_merge_verbosity" >
|
||||
<property name="minimum" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Number of Diff Context Lines:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2" >
|
||||
<widget class="QSpinBox" name="local_gui_diffcontext" >
|
||||
<property name="minimum" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label_4" >
|
||||
<property name="text" >
|
||||
<string>User Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>local_user_name</zorder>
|
||||
<zorder>label_5</zorder>
|
||||
<zorder>local_user_email</zorder>
|
||||
<zorder>local_merge_summary</zorder>
|
||||
<zorder>local_merge_diffstat</zorder>
|
||||
<zorder>label_6</zorder>
|
||||
<zorder>local_merge_verbosity</zorder>
|
||||
<zorder>label</zorder>
|
||||
<zorder>local_gui_diffcontext</zorder>
|
||||
<zorder>label_4</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="globalGroupBox" >
|
||||
<property name="title" >
|
||||
<string>Global (All Repositories)</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label_7" >
|
||||
<property name="text" >
|
||||
<string>User Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2" >
|
||||
<widget class="QLineEdit" name="global_user_name" />
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_8" >
|
||||
<property name="text" >
|
||||
<string>Email Address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2" >
|
||||
<widget class="QLineEdit" name="global_user_email" />
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2" >
|
||||
<widget class="QCheckBox" name="global_merge_summary" >
|
||||
<property name="text" >
|
||||
<string>Summarize Merge Commits</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="3" >
|
||||
<widget class="QCheckBox" name="global_merge_diffstat" >
|
||||
<property name="text" >
|
||||
<string>Show Diffstat After Merge</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label_10" >
|
||||
<property name="text" >
|
||||
<string>Merge Verbosity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2" >
|
||||
<widget class="QSpinBox" name="global_merge_verbosity" >
|
||||
<property name="minimum" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label_9" >
|
||||
<property name="text" >
|
||||
<string>Number of Diff Context Lines:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2" >
|
||||
<widget class="QSpinBox" name="global_gui_diffcontext" >
|
||||
<property name="minimum" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="label_11" >
|
||||
<property name="text" >
|
||||
<string>History browser</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2" >
|
||||
<widget class="QLineEdit" name="global_ugit_historybrowser" >
|
||||
<property name="text" >
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="4" >
|
||||
<widget class="QGroupBox" name="local_groupbox" >
|
||||
<property name="title" >
|
||||
<string>Local Repository</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label_4" >
|
||||
<property name="text" >
|
||||
<string>User Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2" >
|
||||
<widget class="QLineEdit" name="local_user_name" />
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_5" >
|
||||
<property name="text" >
|
||||
<string>Email Address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2" >
|
||||
<widget class="QLineEdit" name="local_user_email" />
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2" >
|
||||
<widget class="QCheckBox" name="local_merge_summary" >
|
||||
<property name="text" >
|
||||
<string>Summarize Merge Commits</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="3" >
|
||||
<widget class="QCheckBox" name="local_merge_diffstat" >
|
||||
<property name="text" >
|
||||
<string>Show Diffstat After Merge</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label_6" >
|
||||
<property name="text" >
|
||||
<string>Merge Verbosity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2" >
|
||||
<widget class="QSpinBox" name="local_merge_verbosity" >
|
||||
<property name="minimum" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Number of Diff Context Lines:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2" >
|
||||
<widget class="QSpinBox" name="local_gui_diffcontext" >
|
||||
<property name="minimum" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4" colspan="3" >
|
||||
<widget class="QGroupBox" name="globalGroupBox" >
|
||||
<property name="title" >
|
||||
<string>Global (All Repositories)</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label_7" >
|
||||
<property name="text" >
|
||||
<string>User Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2" >
|
||||
<widget class="QLineEdit" name="global_user_name" />
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_8" >
|
||||
<property name="text" >
|
||||
<string>Email Address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2" >
|
||||
<widget class="QLineEdit" name="global_user_email" />
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2" >
|
||||
<widget class="QCheckBox" name="global_merge_summary" >
|
||||
<property name="text" >
|
||||
<string>Summarize Merge Commits</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="3" >
|
||||
<widget class="QCheckBox" name="global_merge_diffstat" >
|
||||
<property name="text" >
|
||||
<string>Show Diffstat After Merge</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label_10" >
|
||||
<property name="text" >
|
||||
<string>Merge Verbosity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2" >
|
||||
<widget class="QSpinBox" name="global_merge_verbosity" >
|
||||
<property name="minimum" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label_9" >
|
||||
<property name="text" >
|
||||
<string>Number of Diff Context Lines:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2" >
|
||||
<widget class="QSpinBox" name="global_gui_diffcontext" >
|
||||
<property name="minimum" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="5" >
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>331</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="4" column="5" >
|
||||
<widget class="QPushButton" name="save_button" >
|
||||
<property name="text" >
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="default" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="6" >
|
||||
<widget class="QPushButton" name="cancel_button" >
|
||||
<property name="text" >
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3" colspan="2" >
|
||||
<widget class="QCheckBox" name="global_ugit_savewindowsettings" >
|
||||
<property name="text" >
|
||||
<string>Remember Window Settings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="5" colspan="2" >
|
||||
<widget class="QCheckBox" name="global_ugit_saveatexit" >
|
||||
<property name="text" >
|
||||
<string>Save Window Settings At Exit</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9" >
|
||||
<item>
|
||||
<spacer name="horizontalSpacer" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0" >
|
||||
<size>
|
||||
<width>478</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="save_button" >
|
||||
<property name="text" >
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="default" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancel_button" >
|
||||
<property name="text" >
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ui version="4.0" >
|
||||
<class>push</class>
|
||||
<widget class="QDialog" name="push" >
|
||||
<class>remote</class>
|
||||
<widget class="QDialog" name="remote" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
@@ -40,7 +40,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="remote" />
|
||||
<widget class="QLineEdit" name="remotename" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
@@ -97,7 +97,7 @@
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="push_button" >
|
||||
<widget class="QPushButton" name="action_button" >
|
||||
<property name="text" >
|
||||
<string>Push</string>
|
||||
</property>
|
||||
@@ -119,7 +119,7 @@
|
||||
<connection>
|
||||
<sender>cancel_button</sender>
|
||||
<signal>released()</signal>
|
||||
<receiver>push</receiver>
|
||||
<receiver>remote</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
+4
-11
@@ -167,7 +167,7 @@
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode" >
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTextEdit" name="commit_text" >
|
||||
@@ -204,14 +204,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2" >
|
||||
<item row="0" column="1" >
|
||||
<widget class="QPushButton" name="button_cherrypick" >
|
||||
<property name="text" >
|
||||
<string>Cherry Pick</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3" >
|
||||
<item row="0" column="2" >
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@@ -224,20 +224,13 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="4" >
|
||||
<item row="0" column="3" >
|
||||
<widget class="QPushButton" name="button_close" >
|
||||
<property name="text" >
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" >
|
||||
<widget class="QPushButton" name="button_merge" >
|
||||
<property name="text" >
|
||||
<string>Merge</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
<ui version="4.0" >
|
||||
<class>stash</class>
|
||||
<widget class="QDialog" name="stash" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>683</width>
|
||||
<height>201</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Stash</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" >
|
||||
<item>
|
||||
<widget class="QListWidget" name="stash_list" />
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" >
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_stash_show" >
|
||||
<property name="toolTip" >
|
||||
<string>Show the changes recorded in the selected stash stash as a diff</string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Show</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_stash_save" >
|
||||
<property name="text" >
|
||||
<string>Save</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_stash_apply" >
|
||||
<property name="toolTip" >
|
||||
<string>Apply the selected stash</string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" >
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_stash_done" >
|
||||
<property name="text" >
|
||||
<string>Done</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_stash_drop" >
|
||||
<property name="toolTip" >
|
||||
<string>Remove the selected stash</string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_stash_clear" >
|
||||
<property name="toolTip" >
|
||||
<string>Remove all stashed states</string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Remove All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>button_stash_save</tabstop>
|
||||
<tabstop>button_stash_show</tabstop>
|
||||
<tabstop>button_stash_apply</tabstop>
|
||||
<tabstop>button_stash_drop</tabstop>
|
||||
<tabstop>button_stash_clear</tabstop>
|
||||
<tabstop>button_stash_done</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>button_stash_done</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>stash</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>562</x>
|
||||
<y>292</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>314</x>
|
||||
<y>157</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -38,8 +38,6 @@ def configure(conf):
|
||||
conf.check_tool('pyuic4', 'build')
|
||||
conf.check_tool('po2qm', 'build')
|
||||
|
||||
conf.sub_config('python-git')
|
||||
|
||||
env = conf.env
|
||||
prefix = env['PREFIX']
|
||||
bindir = join(prefix, 'bin')
|
||||
@@ -57,11 +55,28 @@ def configure(conf):
|
||||
env['UGIT_ICONS'] = icons
|
||||
env['UGIT_APPS'] = apps
|
||||
|
||||
try:
|
||||
import git
|
||||
except ImportError:
|
||||
print """
|
||||
ERROR: could not "import git"
|
||||
|
||||
Please install the python-git package
|
||||
or grab GitPython from the cheeseshop:
|
||||
|
||||
http://pypi.python.org/pypi/GitPython
|
||||
|
||||
|
||||
GitPython's git repository can be cloned from gitorious:
|
||||
|
||||
http://gitorious.org/projects/git-python
|
||||
"""
|
||||
return False
|
||||
|
||||
#############################################################################
|
||||
# Build
|
||||
def build(bld):
|
||||
bld.add_subdirs("""
|
||||
python-git
|
||||
scripts
|
||||
ui
|
||||
ugit
|
||||
|
||||
Reference in New Issue
Block a user