Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fde6ced1ed | ||
|
|
1f8777550a | ||
|
|
4b8568dd82 | ||
|
|
4675fff67a | ||
|
|
4f9c2d6174 | ||
|
|
3975b6e624 | ||
|
|
786132ca14 | ||
|
|
956e1bd6a6 | ||
|
|
4cd2e7fdc2 | ||
|
|
815c654ddf | ||
|
|
97b1749b2b | ||
|
|
df5dad891b | ||
|
|
d0b3e9eac3 | ||
|
|
d1c2ae8b81 | ||
|
|
071f77f8bf | ||
|
|
f92236134a | ||
|
|
19761b2393 | ||
|
|
cf3d0f3e7b | ||
|
|
bda109a267 | ||
|
|
8738342179 | ||
|
|
a29d12d0b2 | ||
|
|
45a91d1c48 | ||
|
|
56d70cd52d | ||
|
|
b6fcad2b34 | ||
|
|
2ccb998dc5 | ||
|
|
b91534b983 | ||
|
|
fb519a7547 | ||
|
|
2cba7e82d4 | ||
|
|
e518655a78 | ||
|
|
447d3d357e | ||
|
|
38cff667e4 | ||
|
|
f6d08fdcea | ||
|
|
2d8ba2cc95 | ||
|
|
5cc1a8cefb | ||
|
|
9ac3542781 | ||
|
|
2803194de8 | ||
|
|
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 | ||
|
|
115f043eda | ||
|
|
fd1f23c7ac | ||
|
|
51a7b47e60 | ||
|
|
46f2086b26 | ||
|
|
4d1780983b | ||
|
|
8d893bb747 | ||
|
|
43b4794db7 | ||
|
|
d484f1c012 | ||
|
|
20f00d3579 | ||
|
|
ac00e047a8 | ||
|
|
96e9d4e86e | ||
|
|
fbcf229f33 | ||
|
|
3e0a1c933e | ||
|
|
867a8d929f | ||
|
|
7f0d4d0597 | ||
|
|
7c2e986ff3 | ||
|
|
a9f4f2828c | ||
|
|
c55ca4dd8f | ||
|
|
0e10109cee | ||
|
|
346c710374 | ||
|
|
e7573f1459 | ||
|
|
33b05527ef | ||
|
|
bee3e15896 | ||
|
|
05d7239d7c | ||
|
|
90a5e49c0b | ||
|
|
94d3bc58e8 | ||
|
|
4e4f523abb | ||
|
|
f00bc82915 | ||
|
|
4440eeacee | ||
|
|
bf9a846527 | ||
|
|
a12666ae42 | ||
|
|
6317264a46 | ||
|
|
36739cf1aa | ||
|
|
9d86a739ba | ||
|
|
39b72069d9 | ||
|
|
ac8236e426 | ||
|
|
ef78d94225 | ||
|
|
d0a839f3ce | ||
|
|
ba3716a8f9 | ||
|
|
cbad8ca2e0 | ||
|
|
8a199966d8 | ||
|
|
1c013213a9 | ||
|
|
37c46e6b36 | ||
|
|
a4fc242b83 | ||
|
|
d16c8781e9 | ||
|
|
049bb53ab1 | ||
|
|
b366d30fe3 | ||
|
|
9277ac0228 | ||
|
|
b651cfc9eb | ||
|
|
04fd0fb96a | ||
|
|
18e4af2e20 | ||
|
|
7801694f33 | ||
|
|
c4332d4acb | ||
|
|
1ba197e3c2 | ||
|
|
7c760602ba | ||
|
|
d982638c83 | ||
|
|
fab3d86613 | ||
|
|
d053611998 | ||
|
|
1e5f27ae44 | ||
|
|
72bfcc5217 | ||
|
|
ca3dbb5a9b | ||
|
|
937779e74a | ||
|
|
9c8726e5d9 | ||
|
|
134cbcd90f | ||
|
|
cc2342eff4 | ||
|
|
eddec93589 | ||
|
|
e68feb7038 | ||
|
|
0df6ec29c1 | ||
|
|
74ce128384 | ||
|
|
b626ccced9 | ||
|
|
3df0ba07f3 | ||
|
|
1dc74c8576 | ||
|
|
d1ce3d03e7 | ||
|
|
973f6d43df | ||
|
|
0cac581424 | ||
|
|
ddf46de224 | ||
|
|
a25deac906 | ||
|
|
c8f0b5b117 | ||
|
|
39a0bdc3f2 | ||
|
|
f0644cbeb2 | ||
|
|
6cc000c7cc | ||
|
|
e058abbd3e | ||
|
|
bc4fe64a4c | ||
|
|
61d224db37 | ||
|
|
0f889991bd | ||
|
|
b43aef3383 | ||
|
|
bb35347183 | ||
|
|
f6b3e091db | ||
|
|
7e1014b188 | ||
|
|
bf230dffe7 | ||
|
|
1868f607a7 | ||
|
|
0d0ab3f7b7 | ||
|
|
dd90636a57 | ||
|
|
549f512bfe | ||
|
|
7fa23fd86d | ||
|
|
86392d8da5 | ||
|
|
20d4fe0c90 | ||
|
|
50095592bb | ||
|
|
dafd93f819 | ||
|
|
ad72543683 | ||
|
|
459ad5340d | ||
|
|
84bd89972d | ||
|
|
ba27341fac | ||
|
|
64217dc172 | ||
|
|
b9b8d1b522 | ||
|
|
a319f866c9 | ||
|
|
7be70df4db | ||
|
|
88ffa84bce | ||
|
|
d0c295d1b5 | ||
|
|
2da2f63fa6 | ||
|
|
931b711b24 | ||
|
|
af39d7b2a8 | ||
|
|
edd9f09d33 | ||
|
|
da992c5eac | ||
|
|
f6ac00fd23 | ||
|
|
f865c21531 | ||
|
|
2d00b07a1d | ||
|
|
1bdf4a8485 | ||
|
|
134d8fd62d | ||
|
|
5e13fd7b11 | ||
|
|
1635615135 | ||
|
|
82fec5a1ab | ||
|
|
388824acc4 | ||
|
|
cbc48ae799 | ||
|
|
56d68e4ab7 | ||
|
|
9ebcda2f63 | ||
|
|
33cbe8a644 | ||
|
|
8d9e90e5ac | ||
|
|
c0162c39f0 | ||
|
|
27b02d9038 | ||
|
|
f2a8d5b38d | ||
|
|
285e830726 | ||
|
|
4d68fd54ca | ||
|
|
64836d720d | ||
|
|
5061f1642a | ||
|
|
e00be9bc9f | ||
|
|
75633bebde | ||
|
|
fcd011021d | ||
|
|
74a65e1247 | ||
|
|
00c49f2d33 | ||
|
|
e455dddd21 | ||
|
|
d98bf524ed | ||
|
|
0c42bd8dc5 | ||
|
|
9a4d530126 | ||
|
|
c5edd7b961 | ||
|
|
4993d66235 | ||
|
|
24e3823390 | ||
|
|
2e990b9938 | ||
|
|
ad06fa4523 | ||
|
|
c66b09440f | ||
|
|
2d033e1a3a | ||
|
|
a5c54b49ca | ||
|
|
f7a752918d | ||
|
|
b219628708 | ||
|
|
4c541ad1d7 | ||
|
|
777588a38e | ||
|
|
1dd228f2f7 | ||
|
|
11a2afa6a1 | ||
|
|
cb85b72bda | ||
|
|
ddd433a302 | ||
|
|
f653ab09ed | ||
|
|
51291e33f4 | ||
|
|
b134620872 | ||
|
|
b543bf3614 | ||
|
|
2f4959f80a | ||
|
|
f49cfa3453 | ||
|
|
0d7acc37d3 | ||
|
|
51534988df | ||
|
|
843a468742 | ||
|
|
4c1e003497 | ||
|
|
56f529b028 | ||
|
|
8dd5033daf | ||
|
|
eebdf4a3c6 | ||
|
|
dc1f431728 | ||
|
|
0009e8ecd3 | ||
|
|
6ecc99d4c2 | ||
|
|
9449ff8185 | ||
|
|
03e8c1f68e | ||
|
|
34661f0550 | ||
|
|
c69f7ac51f | ||
|
|
3130210f09 | ||
|
|
e466fa79e6 | ||
|
|
99a9dabd96 | ||
|
|
342ca06050 | ||
|
|
4c248fb616 | ||
|
|
b96cc16811 | ||
|
|
7952b401e0 | ||
|
|
8f452cb1f7 | ||
|
|
16d606baa3 | ||
|
|
49f433209e | ||
|
|
a39223afb7 | ||
|
|
87ef0c5732 | ||
|
|
713b5eef40 | ||
|
|
40ca7b3caa | ||
|
|
f2023d0cb5 | ||
|
|
8fe35787d4 | ||
|
|
fa2820c4f6 | ||
|
|
7f004fac1b | ||
|
|
0927008f84 | ||
|
|
eeae5d3db7 | ||
|
|
bc1b89d3c2 | ||
|
|
52def8364a | ||
|
|
4b6f10231e | ||
|
|
6f8a6a6c5c | ||
|
|
c84b05142d | ||
|
|
fd99b7389f | ||
|
|
e9c3989017 | ||
|
|
5a36ec6752 | ||
|
|
e5c8f2df82 | ||
|
|
98c52056e5 | ||
|
|
66c92acb3d | ||
|
|
992e810a47 | ||
|
|
ea83cae50b | ||
|
|
9506089576 | ||
|
|
a29151bc47 | ||
|
|
7a71feea5a | ||
|
|
6a0f030249 | ||
|
|
02c1ecd6ae | ||
|
|
c16dfea4a9 | ||
|
|
d3ee251795 | ||
|
|
fa332572dc | ||
|
|
8cf09aa29f | ||
|
|
a7b25a7e9d | ||
|
|
a5abccd2af | ||
|
|
01efa2d79e | ||
|
|
3bb791f320 | ||
|
|
478e8d6b2c | ||
|
|
165213bd16 | ||
|
|
b584a72a2c | ||
|
|
cbc29c7771 | ||
|
|
7b01aa042e | ||
|
|
3117a54441 | ||
|
|
3e895e4ca1 | ||
|
|
6415d98a24 | ||
|
|
1100800e7f | ||
|
|
7c81d3b9fa | ||
|
|
0eeeba63cf | ||
|
|
ea861628d4 | ||
|
|
2e64634ba5 | ||
|
|
af3d5d7163 | ||
|
|
48a3dfacfa | ||
|
|
b04d83347e | ||
|
|
a4f6903655 | ||
|
|
9317d78fda | ||
|
|
1507c4e51f | ||
|
|
51c0065075 | ||
|
|
be397d3354 | ||
|
|
6e6230c28d | ||
|
|
038198f41a | ||
|
|
9083f05d61 | ||
|
|
681518f151 | ||
|
|
4408e0ae77 | ||
|
|
56b674f283 | ||
|
|
acdb8d3120 | ||
|
|
8ea60c2317 | ||
|
|
03d7f6feb9 | ||
|
|
1a7ad1b99b | ||
|
|
4144137d41 | ||
|
|
bdfb60d4ba | ||
|
|
6f0d02ab65 | ||
|
|
aa44509af2 | ||
|
|
0dfbfcc671 | ||
|
|
fa4b9d848d | ||
|
|
b4a399f465 | ||
|
|
552662eb96 | ||
|
|
1a375df3fa | ||
|
|
e8beb56f30 | ||
|
|
dbca023099 | ||
|
|
06730ad424 | ||
|
|
d6fa62438c | ||
|
|
d1c922bd75 | ||
|
|
6afbf39187 | ||
|
|
d1a2392211 | ||
|
|
0d679d92b7 | ||
|
|
a5d90073d6 | ||
|
|
d96f9eb2bb | ||
|
|
0dbdfcec43 | ||
|
|
60dab63b96 | ||
|
|
d9b5136e7f | ||
|
|
c40fd604af | ||
|
|
271d4d1caf | ||
|
|
29927d41f3 | ||
|
|
8ac7284b5c | ||
|
|
14c68f4032 | ||
|
|
36a0c83812 | ||
|
|
30dfcfc569 | ||
|
|
158ba54885 | ||
|
|
d676ffce26 | ||
|
|
f42b503ae3 | ||
|
|
e8e8e5d30b | ||
|
|
f8d101cc76 | ||
|
|
e0589d6fa8 | ||
|
|
0067cca128 | ||
|
|
103766573c | ||
|
|
8bbb074657 | ||
|
|
14325ef551 | ||
|
|
394ffee414 | ||
|
|
942367df3e | ||
|
|
0fed8c03a2 | ||
|
|
62c2ca14d5 | ||
|
|
ae843c8aad | ||
|
|
2e7366c86d | ||
|
|
fbb1929a7c | ||
|
|
d342135c14 | ||
|
|
b90ab11119 | ||
|
|
90dc4111dc | ||
|
|
b77cffd178 | ||
|
|
7a381c92d9 |
+16
-3
@@ -1,4 +1,17 @@
|
||||
/build
|
||||
/install
|
||||
/.lock-wscript
|
||||
*.deb
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.rpm
|
||||
*.swp
|
||||
*~
|
||||
/debian/files
|
||||
/debian/bld
|
||||
/debian/cola
|
||||
/installroot
|
||||
/.lock-wscript
|
||||
/obj
|
||||
/Makefile
|
||||
/meta
|
||||
/patches
|
||||
/releases
|
||||
/tmp
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
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.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
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.
|
||||
|
||||
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".
|
||||
|
||||
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 "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.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
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:
|
||||
|
||||
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
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
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.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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. Combined Libraries.
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public 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.
|
||||
|
||||
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.
|
||||
|
||||
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,196 +1,121 @@
|
||||
ugit: ugit's git-gui's inner truffles
|
||||
git-cola: a highly caffeinated git gui
|
||||
|
||||
ugit started out as a python/pyqt re-implementation of git-gui.
|
||||
It has an evolving feature set and aims to be a fully-featured
|
||||
companion to the existing git uis.
|
||||
Cola is a sweet, carbonated git gui known for its
|
||||
sugary flavour and caffeine-inspired features.
|
||||
|
||||
DEPENDENCIES:
|
||||
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
python 2.4.x
|
||||
pyqt 4.3.x
|
||||
pyqtmvc 1.1.x
|
||||
Copyright (C) 2008 David Aguilar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 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 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/
|
||||
|
||||
PyQt 4.3 or newer
|
||||
http://www.riverbankcomputing.co.uk/software/pyqt/intro
|
||||
|
||||
simplejson
|
||||
http://pypi.python.org/pypi/simplejson
|
||||
|
||||
|
||||
ABOUT THE ICONS:
|
||||
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
ACKNOWLEDGEMENTS
|
||||
|
||||
staged.png was blatanly stolen from the "slick" KDE icon set.
|
||||
git-cola includes a copy of the GitPython module:
|
||||
http://gitorious.org/projects/git-python/
|
||||
|
||||
All other icons blatanly stolen from Apache's icons:
|
||||
These icons are covered by the following copyright license:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
INSTALLATION
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
Debian and RPM users can download freshly built binaries here:
|
||||
|
||||
1. Definitions.
|
||||
http://cola.tuxfamily.org/
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
sudo dpkg -i cola_*.deb
|
||||
sudo rpm -ivh cola-*.rpm
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
If you downloaded a tarball, make sure /path/to/cola/bin is in your $PATH.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
HOW TO RUN
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
Take your pick--any of the following incantations work from the command line:
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
git cola
|
||||
git-cola
|
||||
cola
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
Launching cola on repositories outside your $CWD:
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
git cola /repo/path-1 /repo/path-2 .. /repo/path-N
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
BUILDING FROM SOURCE
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
You will need the PyQt development tools (v4.3 or greater):
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
Synopsis:
|
||||
sudo apt-get install pyqt4-dev-tools
|
||||
git clone git://gitorious.org/git-cola/mainline.git cola
|
||||
cd cola
|
||||
./configure --prefix=/usr
|
||||
make
|
||||
sudo make install
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
WINDOWS INSTALLATION
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
1) Install the native Python2.5 and PyQt4 installers (in that order):
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
http://python.org/download/
|
||||
http://www.riverbankcomputing.co.uk/pyqt/download.php
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
3) Install msysgit:
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
http://git.or.cz/gitwiki/WindowsInstall
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
4) Install the GnuWin32 'file' command:
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
http://gnuwin32.sourceforge.net/packages/file.htm
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
Add the installed directory (default: c:\GnuWin32\bin) to your path.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
5) Install simplejson
|
||||
|
||||
http://pypi.python.org/pypi/simplejson
|
||||
|
||||
6) Download cola:
|
||||
|
||||
http://cola.tuxfamily.org/releases/
|
||||
|
||||
7) Install cola (from a Git Bash):
|
||||
|
||||
cd /c/Python25/Lib/site-packages
|
||||
tar xvfz /path/to/cola-win32-*.tar.gz
|
||||
mv cola-win32-* cola
|
||||
|
||||
8) Create an executable in your path for bash, cmd.exe, or both:
|
||||
|
||||
echo '/c/Python25/python.exe /c/Python25/Lib/site-packages/cola/bin/git-cola' >> /usr/bin/cola
|
||||
echo 'c:\\Python25\\python.exe c:\\Python25\\Lib\\site-packages\\bin\\git-cola' > /c/Windows/System32/cola.bat
|
||||
|
||||
This assumes the you have Python/PyQt4 installed in C:/Python25.
|
||||
If this is not the case simply edit the commands accordingly.
|
||||
|
||||
voila, cola on Windows.
|
||||
|
||||
|
||||
NOTE: Windows is not the developer's primary platform.
|
||||
Patches are greatly appreciated.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
sys.path.append (os.path.join (os.path.dirname (__file__), 'ugit'))
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import ugitapi
|
||||
from ugitmodel import GitModel
|
||||
from ugitview import GitView
|
||||
from ugitcontroller import GitController
|
||||
if __name__ == "__main__":
|
||||
app = QtGui.QApplication (sys.argv)
|
||||
model = GitModel()
|
||||
view = GitView (app.activeWindow())
|
||||
ctl = GitController (model, view)
|
||||
view.show()
|
||||
sys.exit(app.exec_())
|
||||
@@ -0,0 +1 @@
|
||||
/.waf-*
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import Action
|
||||
import Object
|
||||
import Params
|
||||
import Utils
|
||||
import Common
|
||||
|
||||
"Converts .po files to the Qt .qm format"
|
||||
|
||||
NAME='po2qm'
|
||||
class po2qm_obj(Object.genobj):
|
||||
|
||||
s_default_ext = ['.po']
|
||||
def __init__(self):
|
||||
Object.genobj.__init__(self, 'other')
|
||||
self.inst_var = 'QMDIR'
|
||||
self.inst_dir = ''
|
||||
self.chmod = 0644
|
||||
|
||||
def apply(self):
|
||||
# create the nodes corresponding to the sources
|
||||
for filename in self.to_list(self.source):
|
||||
|
||||
base, ext = os.path.splitext(filename)
|
||||
if not ext in self.s_default_ext:
|
||||
Params.fatal("unknown file " + filename)
|
||||
|
||||
split_filename = Utils.split_path(filename)
|
||||
node = self.path.find_source_lst(split_filename)
|
||||
|
||||
task = self.create_task(NAME, self.env, 101)
|
||||
task.set_inputs(node)
|
||||
task.set_outputs(node.change_ext('.qm'))
|
||||
|
||||
def install(self):
|
||||
for i in self.m_tasks:
|
||||
current = Params.g_build.m_curdirnode
|
||||
lst=[a.relpath_gen(current) for a in i.m_outputs]
|
||||
Common.install_files(self.inst_var,
|
||||
self.inst_dir,
|
||||
lst,
|
||||
chmod=self.chmod)
|
||||
|
||||
def setup(env):
|
||||
Object.register(NAME, po2qm_obj)
|
||||
Action.simple_action(NAME, '${MSGFMT} ${MSGFMT_FLAGS} -o ${TGT} ${SRC}', color='YELLOW')
|
||||
|
||||
def detect(conf):
|
||||
if not conf.find_program('msgfmt', var='MSGFMT'):
|
||||
Params.fatal('Error: missing msgfmt executable.')
|
||||
return False
|
||||
conf.env['MSGFMT_FLAGS'] = '--qt'
|
||||
conf.env['MSGFMT_EXT'] = ['.po']
|
||||
conf.env['QMDIR'] = os.path.join(conf.env['PREFIX'],
|
||||
'share',
|
||||
Utils.g_module.APPNAME,
|
||||
'qm')
|
||||
return True
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python
|
||||
# Author: David Aguilar
|
||||
|
||||
"""
|
||||
pyuic4: support for generating .py Python scripts from Qt Designer4 .ui files.
|
||||
|
||||
NOTES:
|
||||
|
||||
- If PYQT4_ROOT is given(absolute path), the configuration will look
|
||||
in PYQT4_ROOT/bin first.
|
||||
|
||||
- This module hooks adds a python hook that runs
|
||||
a pyuic4 action when '.ui' files are encoutnered.
|
||||
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
import Action
|
||||
import Object
|
||||
import Params
|
||||
|
||||
import python
|
||||
python.pyobj.s_default_ext.append('.ui')
|
||||
|
||||
def set_options(opt):
|
||||
"""Adds the --pyuic4 build option."""
|
||||
opt.add_option('--pyuic4',
|
||||
type='string',
|
||||
dest='pyuic4',
|
||||
default='',
|
||||
help='path to pyuic4')
|
||||
|
||||
def create_pyuic4_tasks(self, node):
|
||||
"""Creates the tasks to generate python files.
|
||||
The "pyuic4" action is called for this."""
|
||||
|
||||
# Create a pyuic4 task to generate the python .py file
|
||||
pyuic4task = self.create_task('pyuic4')
|
||||
pyuic4task.set_inputs(node)
|
||||
pyuic4task.set_outputs(node.change_ext('.py'))
|
||||
|
||||
# Add the python compilation tasks
|
||||
if self.pyc:
|
||||
task = self.create_task('pyc', self.env, 50)
|
||||
task.set_inputs(node.change_ext('.py'))
|
||||
task.set_outputs(node.change_ext('.pyc'))
|
||||
|
||||
if self.pyo:
|
||||
task = self.create_task('pyo', self.env, 50)
|
||||
task.set_inputs(node.change_ext('.py'))
|
||||
task.set_outputs(node.change_ext('.pyo'))
|
||||
|
||||
def setup(env):
|
||||
"""Creates a python hook and registers it with the environment."""
|
||||
# create the hook action
|
||||
cmd_template = '${PYUIC4} ${PYUIC4_FLAGS} ${SRC} -o ${TGT}'
|
||||
Action.simple_action('pyuic4', cmd_template, 'GREEN')
|
||||
|
||||
# register .ui for use with python
|
||||
Object.hook('py', 'PYUIC4_EXT', create_pyuic4_tasks)
|
||||
|
||||
def detect(conf):
|
||||
env = conf.env
|
||||
opt = Params.g_options
|
||||
|
||||
pyuic4 = None
|
||||
try:
|
||||
pyuic4 = opt.pyuic4
|
||||
except:
|
||||
pass
|
||||
|
||||
if not pyuic4:
|
||||
qtdir = os.environ.get('PYQT4_ROOT', '')
|
||||
if qtdir:
|
||||
binpath = [qtdir] + os.environ['PATH'].split(':')
|
||||
else:
|
||||
binpath = os.environ['PATH'].split(':')
|
||||
|
||||
for f in ['pyuic4', 'pyuic-qt4', 'pyuic']:
|
||||
pyuic4 = conf.find_program(f, path_list=binpath)
|
||||
if pyuic4:
|
||||
break
|
||||
|
||||
if not pyuic4:
|
||||
conf.check_message('pyuic4 binary', '(not found)', 0)
|
||||
Params.fatal('Error: missing PyQt4 development tools.')
|
||||
return False
|
||||
|
||||
# Set the path to pyuic4
|
||||
env['PYUIC4'] = pyuic4
|
||||
env['PYUIC4_EXT'] = ['.ui']
|
||||
env['PYUIC4_FLAGS'] = '-x'
|
||||
|
||||
vercmd = env['PYUIC4'] + ' --version 2>&1'
|
||||
version = os.popen(vercmd).read().strip().split(' ')[-1]
|
||||
version = version.split('.')[0]
|
||||
if not version.isdigit() or int(version) < 4:
|
||||
conf.check_message('pyuic4 version', '(not found or too old)', 0,
|
||||
option= '(%s)' % version)
|
||||
return False
|
||||
|
||||
conf.check_message('pyuic4 version', '', 1, option='(%s)' % version)
|
||||
|
||||
# all tests passed
|
||||
return True
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,877 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import glob
|
||||
import platform
|
||||
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.QtGui import QDialog
|
||||
from PyQt4.QtGui import QMessageBox
|
||||
from PyQt4.QtGui import QMenu
|
||||
from PyQt4.QtGui import QFont
|
||||
|
||||
from cola import utils
|
||||
from cola import qtutils
|
||||
from cola import defaults
|
||||
from cola.qobserver import QObserver
|
||||
|
||||
# controllers namespace
|
||||
import search
|
||||
from util import logger
|
||||
from remote import remote_action
|
||||
from util import choose_from_list
|
||||
from util import choose_from_combo
|
||||
from util import select_commits
|
||||
from util import update_options
|
||||
from repobrowser import browse_git_branch
|
||||
from createbranch import create_new_branch
|
||||
from search import search_commits
|
||||
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):
|
||||
"""Manages the interaction between models and views."""
|
||||
|
||||
MODE_NONE = 0
|
||||
MODE_WORKTREE = 1
|
||||
MODE_INDEX = 2
|
||||
MODE_BRANCH = 3
|
||||
|
||||
def init(self, model, view):
|
||||
"""
|
||||
State machine:
|
||||
Modes are:
|
||||
none -> do nothing, disables most context menus
|
||||
branch -> diff against another branch and selectively choose changes
|
||||
worktree -> selectively add working changes to the index
|
||||
index -> selectively remove changes from the index
|
||||
"""
|
||||
self.mode = Controller.MODE_NONE
|
||||
|
||||
# parent-less log window
|
||||
qtutils.LOGGER = logger()
|
||||
|
||||
# Unstaged changes context menu
|
||||
view.unstaged.contextMenuEvent = self.unstaged_context_menu_event
|
||||
|
||||
# Diff display context menu
|
||||
view.display_text.contextMenuEvent = self.diff_context_menu_event
|
||||
|
||||
# Binds model params to their equivalent view widget
|
||||
self.add_observables('commitmsg', 'staged', 'unstaged')
|
||||
|
||||
# When a model attribute changes, this runs a specific action
|
||||
self.add_actions(staged = self.action_staged)
|
||||
self.add_actions(unstaged = self.action_unstaged)
|
||||
self.add_actions(global_cola_fontdiff = self.update_diff_font)
|
||||
self.add_actions(global_cola_fontui = self.update_ui_font)
|
||||
|
||||
self.add_callbacks(
|
||||
# Push Buttons
|
||||
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,
|
||||
# Checkboxes
|
||||
untracked_checkbox = self.rescan,
|
||||
|
||||
# File Menu
|
||||
menu_quit = self.quit_app,
|
||||
menu_open_repo = self.open_repo,
|
||||
menu_manage_bookmarks = manage_bookmarks,
|
||||
menu_save_bookmark = save_bookmark,
|
||||
|
||||
# Edit Menu
|
||||
menu_options = self.options,
|
||||
menu_cut = self.view.action_cut,
|
||||
menu_copy = self.view.action_copy,
|
||||
menu_paste = self.view.action_paste,
|
||||
menu_delete = self.view.action_delete,
|
||||
menu_select_all = self.view.action_select_all,
|
||||
menu_undo = self.view.action_undo,
|
||||
menu_redo = self.view.action_redo,
|
||||
|
||||
# Search Menu
|
||||
menu_search_grep = self.grep,
|
||||
menu_search_revision =
|
||||
self.gen_search( search.REVISION_ID ),
|
||||
menu_search_revision_range =
|
||||
self.gen_search( search.REVISION_RANGE ),
|
||||
menu_search_message =
|
||||
self.gen_search( search.MESSAGE ),
|
||||
menu_search_path =
|
||||
self.gen_search( search.PATH, True ),
|
||||
menu_search_date_range =
|
||||
self.gen_search( search.DATE_RANGE ),
|
||||
menu_search_diff =
|
||||
self.gen_search( search.DIFF ),
|
||||
menu_search_author =
|
||||
self.gen_search( search.AUTHOR ),
|
||||
menu_search_committer =
|
||||
self.gen_search( search.COMMITTER ),
|
||||
|
||||
# Merge Menu
|
||||
menu_merge_local =
|
||||
lambda: local_merge( self.model, self.view ),
|
||||
menu_merge_abort =
|
||||
lambda: abort_merge( self.model, self.view ),
|
||||
|
||||
# Repository Menu
|
||||
menu_visualize_current = self.viz_current,
|
||||
menu_visualize_all = self.viz_all,
|
||||
menu_browse_commits = self.browse_commits,
|
||||
menu_browse_branch = self.browse_current,
|
||||
menu_browse_other_branch = self.browse_other,
|
||||
|
||||
# Branch Menu
|
||||
menu_create_branch = self.branch_create,
|
||||
menu_checkout_branch = self.checkout_branch,
|
||||
menu_diff_branch = self.diff_branch,
|
||||
menu_diffedit_branch = self.diffedit_branch,
|
||||
|
||||
# Commit Menu
|
||||
menu_rescan = self.rescan,
|
||||
menu_delete_branch = self.branch_delete,
|
||||
menu_rebase_branch = self.rebase,
|
||||
menu_commit = self.commit,
|
||||
menu_stage_selected = self.stage_selected,
|
||||
menu_unstage_selected = self.unstage_selected,
|
||||
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,
|
||||
menu_stage_modified =
|
||||
lambda: self.log(self.model.stage_modified()),
|
||||
menu_stage_untracked =
|
||||
lambda: self.log(self.model.stage_untracked()),
|
||||
menu_unstage_all =
|
||||
lambda: self.log(self.model.unstage_all()),
|
||||
)
|
||||
|
||||
# Delegate window events here
|
||||
view.moveEvent = self.move_event
|
||||
view.resizeEvent = self.resize_event
|
||||
view.closeEvent = self.quit_app
|
||||
view.staged.mousePressEvent = self.click_staged
|
||||
view.unstaged.mousePressEvent = self.click_unstaged
|
||||
|
||||
# These are vanilla signal/slots since QObserver
|
||||
# is already handling these signals.
|
||||
self.connect(view.unstaged,
|
||||
'itemDoubleClicked(QListWidgetItem*)',
|
||||
self.stage_selected)
|
||||
self.connect(view.staged,
|
||||
'itemDoubleClicked(QListWidgetItem*)',
|
||||
self.unstage_selected)
|
||||
|
||||
# Toolbar log button
|
||||
self.connect(view.toolbar_show_log,
|
||||
'triggered()', self.show_log)
|
||||
|
||||
self.connect(view.diff_dock,
|
||||
'topLevelChanged(bool)',
|
||||
lambda(b): self.setwindow(view.diff_dock, b))
|
||||
|
||||
self.connect(view.editor_dock,
|
||||
'topLevelChanged(bool)',
|
||||
lambda(b): self.setwindow(view.editor_dock, b))
|
||||
|
||||
self.connect(view.status_dock,
|
||||
'topLevelChanged(bool)',
|
||||
lambda(b): self.setwindow(view.status_dock, b))
|
||||
|
||||
self.init_log_window()
|
||||
self.load_gui_settings()
|
||||
self.rescan()
|
||||
self.refresh_view('global_cola_fontdiff', 'global_cola_fontui')
|
||||
self.start_inotify_thread()
|
||||
|
||||
def setwindow(self, dock, isfloating):
|
||||
if isfloating:
|
||||
if platform.system() != 'Windows':
|
||||
flags = ( QtCore.Qt.Window
|
||||
| QtCore.Qt.FramelessWindowHint )
|
||||
dock.setWindowFlags( flags )
|
||||
dock.show()
|
||||
|
||||
#####################################################################
|
||||
# handle when the listitem icons are clicked
|
||||
def click_event(self, widget, action_callback, event):
|
||||
result = QtGui.QListWidget.mousePressEvent(widget, event)
|
||||
xpos = event.pos().x()
|
||||
if xpos > 5 and xpos < 20:
|
||||
action_callback()
|
||||
return result
|
||||
|
||||
def click_staged(self, event):
|
||||
return self.click_event(self.view.staged,
|
||||
self.unstage_selected, event)
|
||||
|
||||
def click_unstaged(self, event):
|
||||
return self.click_event(self.view.unstaged,
|
||||
self.stage_selected, event)
|
||||
|
||||
#####################################################################
|
||||
# event() is called in response to messages from the inotify thread
|
||||
def event(self, msg):
|
||||
if msg.type() == defaults.INOTIFY_EVENT:
|
||||
self.rescan()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
#####################################################################
|
||||
# Actions triggered during model updates
|
||||
|
||||
def action_staged(self, widget):
|
||||
qtutils.update_listwidget(widget,
|
||||
self.model.get_staged(),
|
||||
staged=True)
|
||||
self.view.editor_dock.raise_()
|
||||
|
||||
def action_unstaged(self, widget):
|
||||
qtutils.update_listwidget(widget,
|
||||
self.model.get_modified(),
|
||||
staged=False)
|
||||
if self.view.untracked_checkbox.isChecked():
|
||||
qtutils.update_listwidget(widget,
|
||||
self.model.get_untracked(),
|
||||
staged=False,
|
||||
append=True,
|
||||
untracked=True)
|
||||
|
||||
#####################################################################
|
||||
# Qt callbacks
|
||||
def gen_search(self, searchtype, browse=False):
|
||||
def search_handler():
|
||||
search_commits(self.model, searchtype, browse)
|
||||
return search_handler
|
||||
|
||||
def grep(self):
|
||||
txt, ok = qtutils.input("grep")
|
||||
if not ok:
|
||||
return
|
||||
stuff = self.model.grep(txt)
|
||||
self.view.display_text.setText(stuff)
|
||||
self.view.diff_dock.raise_()
|
||||
|
||||
def show_log(self, *rest):
|
||||
qtutils.toggle_log_window()
|
||||
|
||||
def options(self):
|
||||
update_options(self.model, self.view)
|
||||
|
||||
def branch_create(self):
|
||||
if create_new_branch(self.model, self.view):
|
||||
self.rescan()
|
||||
|
||||
def branch_delete(self):
|
||||
branch = choose_from_combo('Delete Branch',
|
||||
self.view,
|
||||
self.model.get_local_branches())
|
||||
if not branch:
|
||||
return
|
||||
self.log(self.model.delete_branch(branch))
|
||||
|
||||
def browse_current(self):
|
||||
branch = self.model.get_currentbranch()
|
||||
browse_git_branch(self.model, self.view, branch)
|
||||
|
||||
def browse_other(self):
|
||||
# Prompt for a branch to browse
|
||||
branch = choose_from_combo('Browse Branch Files',
|
||||
self.view,
|
||||
self.model.get_all_branches())
|
||||
if not branch:
|
||||
return
|
||||
# Launch the repobrowser
|
||||
browse_git_branch(self.model, self.view, branch)
|
||||
|
||||
def checkout_branch(self):
|
||||
branch = choose_from_combo('Checkout Branch',
|
||||
self.view,
|
||||
self.model.get_local_branches())
|
||||
if not branch:
|
||||
return
|
||||
self.log(self.model.checkout(branch))
|
||||
|
||||
def browse_commits(self):
|
||||
self.select_commits_gui(self.tr('Browse Commits'),
|
||||
*self.model.log_helper(all=True))
|
||||
|
||||
def cherry_pick(self):
|
||||
commits = self.select_commits_gui(self.tr('Cherry-Pick Commits'),
|
||||
*self.model.log_helper(all=True))
|
||||
if not commits:
|
||||
return
|
||||
self.log(self.model.cherry_pick_list(commits))
|
||||
|
||||
def commit(self):
|
||||
msg = self.model.get_commitmsg()
|
||||
if not msg:
|
||||
error_msg = self.tr(""
|
||||
+ "Please supply a commit message.\n"
|
||||
+ "\n"
|
||||
+ "A good commit message has the following format:\n"
|
||||
+ "\n"
|
||||
+ "- First line: Describe in one sentence what you did.\n"
|
||||
+ "- Second line: Blank\n"
|
||||
+ "- Remaining lines: Describe why this change is good.\n")
|
||||
self.log(error_msg)
|
||||
return
|
||||
|
||||
files = self.model.get_staged()
|
||||
if not files and not self.view.amend_radio.isChecked():
|
||||
error_msg = self.tr(""
|
||||
+ "No changes to commit.\n"
|
||||
+ "\n"
|
||||
+ "You must stage at least 1 file before you can commit.\n")
|
||||
self.log(error_msg)
|
||||
return
|
||||
|
||||
# Perform the commit
|
||||
amend = self.view.amend_radio.isChecked()
|
||||
output = self.model.commit_with_msg(msg, amend=amend)
|
||||
|
||||
# Reset state
|
||||
self.view.new_commit_radio.setChecked(True)
|
||||
self.view.amend_radio.setChecked(False)
|
||||
self.model.set_commitmsg('')
|
||||
self.log(output)
|
||||
|
||||
def view_diff(self, staged=True):
|
||||
if staged:
|
||||
self.mode = Controller.MODE_INDEX
|
||||
widget = self.view.staged
|
||||
else:
|
||||
self.mode = Controller.MODE_WORKTREE
|
||||
widget = self.view.unstaged
|
||||
row, selected = qtutils.get_selected_row(widget)
|
||||
if not selected:
|
||||
self.mode = Controller.MODE_NONE
|
||||
self.view.reset_display()
|
||||
return
|
||||
(diff, status) = self.model.get_diff_and_status(row, staged=staged)
|
||||
|
||||
self.view.set_display(diff)
|
||||
self.view.set_info(self.tr(status))
|
||||
self.view.diff_dock.raise_()
|
||||
|
||||
def edit_file(self, staged=True):
|
||||
if staged:
|
||||
widget = self.view.staged
|
||||
else:
|
||||
widget = self.view.unstaged
|
||||
row, selected = qtutils.get_selected_row(widget)
|
||||
if not selected:
|
||||
return
|
||||
if staged:
|
||||
filename = self.model.get_staged()[row]
|
||||
else:
|
||||
filename = self.model.get_unstaged()[row]
|
||||
utils.fork(self.model.get_editor(), filename)
|
||||
|
||||
def edit_diff(self, staged=True):
|
||||
if staged:
|
||||
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:
|
||||
filename = self.model.get_staged()[row]
|
||||
else:
|
||||
filename = self.model.get_unstaged()[row]
|
||||
|
||||
contents = self.model.show("HEAD:"+filename, with_raw_output=True)
|
||||
tmpfile = self.model.get_tmp_filename(filename)
|
||||
fh = open(tmpfile, 'w')
|
||||
fh.write(contents)
|
||||
fh.close()
|
||||
utils.fork(self.model.get_diffeditor(), filename, tmpfile)
|
||||
|
||||
# use *rest to handle being called from different signals
|
||||
def diff_staged(self, *rest):
|
||||
self.view_diff(staged=True)
|
||||
|
||||
# use *rest to handle being called from different signals
|
||||
def diff_unstaged(self, *rest):
|
||||
self.view_diff(staged=False)
|
||||
|
||||
def export_patches(self):
|
||||
(revs, summaries) = self.model.log_helper()
|
||||
to_export = self.select_commits_gui(self.tr('Export Patches'),
|
||||
revs, summaries)
|
||||
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 cola session"""
|
||||
dirname = qtutils.opendir_dialog(self.view,
|
||||
'Open Git Repository...',
|
||||
os.getcwd())
|
||||
if dirname:
|
||||
utils.fork(sys.argv[0], dirname)
|
||||
|
||||
def quit_app(self, *args):
|
||||
"""Save config settings and cleanup any inotify threads."""
|
||||
|
||||
if self.model.save_at_exit():
|
||||
self.model.save_gui_settings()
|
||||
qtutils.close_log_window()
|
||||
pattern = self.model.get_tmp_file_pattern()
|
||||
for filename in glob.glob(pattern):
|
||||
os.unlink(filename)
|
||||
if self.inotify_thread and self.inotify_thread.isRunning():
|
||||
self.inotify_thread.abort = True
|
||||
self.inotify_thread.wait()
|
||||
self.view.close()
|
||||
|
||||
def load_commitmsg(self):
|
||||
file = qtutils.open_dialog(self.view,
|
||||
'Load Commit Message...',
|
||||
defaults.DIRECTORY)
|
||||
if file:
|
||||
defaults.DIRECTORY = os.path.dirname(file)
|
||||
slushy = utils.slurp(file)
|
||||
if slushy:
|
||||
self.model.set_commitmsg(slushy)
|
||||
|
||||
def rebase(self):
|
||||
branch = choose_from_combo('Rebase Branch',
|
||||
self.view,
|
||||
self.model.get_local_branches())
|
||||
if not branch:
|
||||
return
|
||||
self.log(self.model.rebase(branch))
|
||||
|
||||
# use *rest to handle being called from the checkbox signal
|
||||
def rescan(self, *rest):
|
||||
"""Populates view widgets with results from 'git status.'"""
|
||||
|
||||
# save entire selection
|
||||
unstaged = qtutils.get_selection_list(self.view.unstaged,
|
||||
self.model.get_unstaged())
|
||||
staged = qtutils.get_selection_list(self.view.staged,
|
||||
self.model.get_staged())
|
||||
|
||||
scrollbar = self.view.display_text.verticalScrollBar()
|
||||
scrollvalue = scrollbar.value()
|
||||
|
||||
# save selected item
|
||||
unstageditem = qtutils.get_selected_item(self.view.unstaged,
|
||||
self.model.get_unstaged())
|
||||
|
||||
stageditem = qtutils.get_selected_item(self.view.staged,
|
||||
self.model.get_staged())
|
||||
|
||||
# get new values
|
||||
self.model.update_status()
|
||||
|
||||
# restore selection
|
||||
update_staged = False
|
||||
update_unstaged = False
|
||||
updated_unstaged = self.model.get_unstaged()
|
||||
updated_staged = self.model.get_staged()
|
||||
|
||||
for item in unstaged:
|
||||
if item in updated_unstaged:
|
||||
idx = updated_unstaged.index(item)
|
||||
listitem = self.view.unstaged.item(idx)
|
||||
if listitem:
|
||||
listitem.setSelected(True)
|
||||
self.view.unstaged.setItemSelected(listitem, True)
|
||||
update_unstaged = True
|
||||
self.view.unstaged.update()
|
||||
for item in staged:
|
||||
if item in updated_staged:
|
||||
idx = updated_staged.index(item)
|
||||
listitem = self.view.staged.item(idx)
|
||||
if listitem:
|
||||
listitem.setSelected(True)
|
||||
self.view.staged.setItemSelected(listitem, True)
|
||||
update_staged = True
|
||||
|
||||
# restore selected item
|
||||
if update_unstaged and unstageditem:
|
||||
idx = updated_unstaged.index(unstageditem)
|
||||
item = self.view.unstaged.item(idx)
|
||||
self.view.unstaged.setCurrentItem(item)
|
||||
self.view_diff(False)
|
||||
scrollbar.setValue(scrollvalue)
|
||||
elif update_staged and stageditem:
|
||||
idx = updated_staged.index(stageditem)
|
||||
item = self.view.staged.item(idx)
|
||||
self.view.staged.setCurrentItem(item)
|
||||
self.view_diff(True)
|
||||
scrollbar.setValue(scrollvalue)
|
||||
|
||||
# Update the title with the current branch
|
||||
self.view.setWindowTitle('%s [%s]' % (
|
||||
self.model.get_project(),
|
||||
self.model.get_currentbranch()))
|
||||
|
||||
# Check if there's a message file in .git/
|
||||
merge_msg_path = self.model.get_merge_message_path()
|
||||
if merge_msg_path is None:
|
||||
return
|
||||
|
||||
# A merge message file exists.
|
||||
set_msg = False
|
||||
if self.model.get_commitmsg():
|
||||
# The commit message editor contains data.
|
||||
# Prompt before overwriting the commit message
|
||||
# with the contents of the merge message.
|
||||
answer = qtutils.question(self.view,
|
||||
self.tr('Import Commit Message?'),
|
||||
self.tr('A commit message from an in-progress'
|
||||
+' merge was found.\nImport it?'))
|
||||
if answer:
|
||||
set_msg = True
|
||||
else:
|
||||
set_msg = True
|
||||
# Set the new commit message
|
||||
if set_msg:
|
||||
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):
|
||||
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."""
|
||||
self.mode = Controller.MODE_NONE
|
||||
self.view.set_info(self.tr('Diffstat'))
|
||||
self.view.set_display(self.model.diffstat())
|
||||
|
||||
def show_index(self):
|
||||
self.mode = Controller.MODE_NONE
|
||||
self.view.set_info(self.tr('Index'))
|
||||
self.view.set_display(self.model.diffindex())
|
||||
|
||||
#####################################################################
|
||||
def diffedit_branch(self):
|
||||
branch = choose_from_combo('Select Branch',
|
||||
self.view,
|
||||
self.model.get_all_branches())
|
||||
if not branch:
|
||||
return
|
||||
zfiles_str = self.model.diff(branch, name_only=True, z=True)
|
||||
files = zfiles_str.split('\0')
|
||||
filename = choose_from_list('Select File', self.view, files)
|
||||
if not filename:
|
||||
return
|
||||
contents = self.model.show('%s:%s' % (branch, filename), with_raw_output=True)
|
||||
tmpfile = self.model.get_tmp_filename(filename)
|
||||
fh = open(tmpfile, 'w')
|
||||
fh.write(contents)
|
||||
fh.close()
|
||||
utils.fork(self.model.get_diffeditor(), filename, tmpfile)
|
||||
|
||||
# Set state machine to branch mode
|
||||
self.mode = Controller.MODE_NONE
|
||||
|
||||
#####################################################################
|
||||
# diff gui
|
||||
def diff_branch(self):
|
||||
branch = choose_from_combo('Select Branch',
|
||||
self.view,
|
||||
self.model.get_all_branches())
|
||||
if not branch:
|
||||
return
|
||||
zfiles_str = self.model.diff(branch, name_only=True, z=True)
|
||||
files = zfiles_str.split('\0')
|
||||
filename = choose_from_list('Select File', self.view, files)
|
||||
if not filename:
|
||||
return
|
||||
status = ('Diff of "%s" between the work tree and %s'
|
||||
% (filename, branch))
|
||||
|
||||
diff = self.model.diff_helper(filename=filename,
|
||||
cached=False,
|
||||
reverse=True,
|
||||
branch=branch)
|
||||
self.view.set_display(diff)
|
||||
self.view.set_info(self.tr(status))
|
||||
self.view.diff_dock.raise_()
|
||||
|
||||
# Set state machine to branch mode
|
||||
self.mode = Controller.MODE_BRANCH
|
||||
self.branch = branch
|
||||
self.filename = filename
|
||||
|
||||
def process_diff_selection(self, items, widget,
|
||||
cached=True, selected=False, reverse=True):
|
||||
|
||||
if self.mode == Controller.MODE_BRANCH:
|
||||
branch = self.branch
|
||||
filename = self.filename
|
||||
parser = utils.DiffParser(self.model,
|
||||
filename=filename,
|
||||
cached=False,
|
||||
branch=branch)
|
||||
offset, selection = self.view.diff_selection()
|
||||
parser.process_diff_selection(selected, offset, selection,
|
||||
branch=True)
|
||||
self.rescan()
|
||||
else:
|
||||
filename = qtutils.get_selected_item(widget, items)
|
||||
if not filename:
|
||||
return
|
||||
parser = utils.DiffParser(self.model,
|
||||
filename=filename,
|
||||
cached=cached)
|
||||
offset, selection = self.view.diff_selection()
|
||||
parser.process_diff_selection(selected, offset, selection)
|
||||
self.rescan()
|
||||
|
||||
def stage_hunk(self):
|
||||
self.process_diff_selection(self.model.get_unstaged(),
|
||||
self.view.unstaged,
|
||||
cached=False)
|
||||
|
||||
def stage_hunk_selection(self):
|
||||
self.process_diff_selection(self.model.get_unstaged(),
|
||||
self.view.unstaged,
|
||||
cached=False,
|
||||
selected=True)
|
||||
|
||||
def unstage_hunk(self, cached=True):
|
||||
self.process_diff_selection(self.model.get_staged(),
|
||||
self.view.staged,
|
||||
cached=True)
|
||||
|
||||
def unstage_hunk_selection(self):
|
||||
self.process_diff_selection(self.model.get_staged(),
|
||||
self.view.staged,
|
||||
cached=True,
|
||||
selected=True)
|
||||
|
||||
# #######################################################################
|
||||
# end diff gui
|
||||
|
||||
# *rest handles being called from different signals
|
||||
def stage_selected(self,*rest):
|
||||
"""Use "git add" to add items to the git index.
|
||||
This is a thin wrapper around map_to_listwidget."""
|
||||
command = self.model.add_or_remove
|
||||
widget = self.view.unstaged
|
||||
items = self.model.get_unstaged()
|
||||
self.map_to_listwidget(command, widget, items)
|
||||
|
||||
# *rest handles being called from different signals
|
||||
def unstage_selected(self, *rest):
|
||||
"""Use "git reset" to remove items from the git index.
|
||||
This is a thin wrapper around map_to_listwidget."""
|
||||
command = self.model.reset_helper
|
||||
widget = self.view.staged
|
||||
items = self.model.get_staged()
|
||||
self.map_to_listwidget(command, widget, items)
|
||||
|
||||
def undo_changes(self):
|
||||
"""Reverts local changes back to whatever's in HEAD."""
|
||||
widget = self.view.unstaged
|
||||
items = self.model.get_unstaged()
|
||||
potential_items = qtutils.get_selection_list(widget, items)
|
||||
items_to_undo = []
|
||||
untracked = self.model.get_untracked()
|
||||
for item in potential_items:
|
||||
if item not in untracked:
|
||||
items_to_undo.append(item)
|
||||
if items_to_undo:
|
||||
if not qtutils.question(self.view,
|
||||
self.tr('Destroy Local Changes?'),
|
||||
self.tr('This operation will drop all '
|
||||
+'uncommitted changes. '
|
||||
+'Continue?'),
|
||||
default=False):
|
||||
return
|
||||
|
||||
output = self.model.checkout('HEAD', '--', *items_to_undo)
|
||||
self.log('git checkout HEAD -- '
|
||||
+' '.join(items_to_undo)
|
||||
+'\n' + output)
|
||||
else:
|
||||
msg = 'No files selected for checkout from HEAD.'
|
||||
self.log(self.tr(msg))
|
||||
|
||||
def viz_all(self):
|
||||
"""Visualizes the entire git history using gitk."""
|
||||
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_history_browser()
|
||||
utils.fork(browser, self.model.get_currentbranch())
|
||||
|
||||
def move_event(self, event):
|
||||
defaults.X = event.pos().x()
|
||||
defaults.Y = event.pos().y()
|
||||
|
||||
def resize_event(self, event):
|
||||
defaults.WIDTH = event.size().width()
|
||||
defaults.HEIGHT = event.size().height()
|
||||
|
||||
def load_gui_settings(self):
|
||||
if not self.model.remember_gui_settings():
|
||||
return
|
||||
(w,h,x,y,st0,st1,sb0,sb1) = self.model.get_window_geom()
|
||||
self.view.resize(w,h)
|
||||
self.view.move(x,y)
|
||||
|
||||
def log(self, output, rescan=True, quiet=False):
|
||||
"""Logs output and optionally rescans for changes."""
|
||||
qtutils.log(output, quiet=quiet, doraise=False)
|
||||
if rescan:
|
||||
self.rescan()
|
||||
|
||||
def map_to_listwidget(self, command, widget, items):
|
||||
"""This is a helper method that retrieves the current
|
||||
selection list, applies a command to that list,
|
||||
displays a dialog showing the output of that command,
|
||||
and calls rescan to pickup changes."""
|
||||
apply_items = qtutils.get_selection_list(widget, items)
|
||||
output = command(*apply_items)
|
||||
self.log(output, quiet=True)
|
||||
|
||||
def unstaged_context_menu_event(self, event):
|
||||
menu = self.unstaged_context_menu_setup()
|
||||
unstaged = self.view.unstaged
|
||||
menu.exec_(unstaged.mapToGlobal(event.pos()))
|
||||
|
||||
def unstaged_context_menu_setup(self):
|
||||
unstaged_item = qtutils.get_selected_item(self.view.unstaged,
|
||||
self.model.get_unstaged())
|
||||
is_tracked = unstaged_item not in self.model.get_untracked()
|
||||
enable_staging = self.mode == Controller.MODE_WORKTREE
|
||||
enable_undo = enable_staging and is_tracked
|
||||
|
||||
menu = QMenu(self.view)
|
||||
|
||||
if enable_staging:
|
||||
menu.addAction(self.tr('Stage Selected'), self.stage_selected)
|
||||
if enable_undo:
|
||||
menu.addAction(self.tr('Undo Local Changes'), self.undo_changes)
|
||||
menu.addAction(self.tr('Launch Editor'),
|
||||
lambda: self.edit_file(staged=False))
|
||||
if enable_staging:
|
||||
menu.addAction(self.tr('Launch Diff Editor'),
|
||||
lambda: self.edit_diff(staged=False))
|
||||
return menu
|
||||
|
||||
def diff_context_menu_event(self, event):
|
||||
menu = self.diff_context_menu_setup()
|
||||
textedit = self.view.display_text
|
||||
menu.exec_(textedit.mapToGlobal(event.pos()))
|
||||
|
||||
def diff_context_menu_setup(self):
|
||||
menu = QMenu(self.view)
|
||||
if self.mode == Controller.MODE_WORKTREE:
|
||||
unstaged_item = qtutils.get_selected_item(self.view.unstaged,
|
||||
self.model.get_unstaged())
|
||||
is_tracked= (unstaged_item
|
||||
and unstaged_item not in self.model.get_untracked())
|
||||
if is_tracked:
|
||||
menu.addAction(self.tr('Stage Hunk For Commit'),
|
||||
self.stage_hunk)
|
||||
menu.addAction(self.tr('Stage Selected Lines'),
|
||||
self.stage_hunk_selection)
|
||||
|
||||
elif self.mode == Controller.MODE_INDEX:
|
||||
menu.addAction(self.tr('Unstage Hunk From Commit'),
|
||||
self.unstage_hunk)
|
||||
menu.addAction(self.tr('Unstage Selected Lines'),
|
||||
self.unstage_hunk_selection)
|
||||
|
||||
elif self.mode == Controller.MODE_BRANCH:
|
||||
menu.addAction(self.tr('Apply Diff To Work Tree'),
|
||||
self.stage_hunk)
|
||||
menu.addAction(self.tr('Apply Diff Selection To Work Tree'),
|
||||
self.stage_hunk_selection)
|
||||
|
||||
menu.addAction(self.tr('Copy'),
|
||||
self.view.copy_display)
|
||||
return menu
|
||||
|
||||
def select_commits_gui(self, title, revs, summaries):
|
||||
return select_commits(self.model, self.view, title, revs, summaries)
|
||||
|
||||
def update_diff_font(self):
|
||||
font = self.model.get_global_cola_fontdiff()
|
||||
if not font:
|
||||
return
|
||||
qfont = QFont()
|
||||
qfont.fromString(font)
|
||||
self.view.display_text.setFont(qfont)
|
||||
self.view.commitmsg.setFont(qfont)
|
||||
|
||||
def update_ui_font(self):
|
||||
font = self.model.get_global_cola_fontui()
|
||||
if not font:
|
||||
return
|
||||
qfont = QFont()
|
||||
qfont.fromString(font)
|
||||
QtGui.qApp.setFont(qfont)
|
||||
|
||||
def init_log_window(self):
|
||||
branch = self.model.get_currentbranch()
|
||||
version = defaults.VERSION
|
||||
qtutils.log(self.model.get_git_version()
|
||||
+'\ncola version '+ version
|
||||
+'\nCurrent Branch: '+ branch)
|
||||
|
||||
def start_inotify_thread(self):
|
||||
# Do we have inotify? If not, return.
|
||||
# Recommend installing inotify if we're on Linux.
|
||||
self.inotify_thread = None
|
||||
try:
|
||||
from cola.inotify import GitNotifier
|
||||
qtutils.log(self.tr('inotify support: enabled'))
|
||||
except ImportError:
|
||||
import platform
|
||||
if platform.system() == 'Linux':
|
||||
msg = self.tr('inotify: disabled\n'
|
||||
'Note: To enable inotify, '
|
||||
'install python-pyinotify.\n')
|
||||
|
||||
plat = platform.platform().lower()
|
||||
if 'debian' in plat or 'ubuntu' in plat:
|
||||
msg += self.tr('On Debian or Ubuntu systems, '
|
||||
'try: sudo apt-get install '
|
||||
'python-pyinotify')
|
||||
qtutils.log(msg)
|
||||
return
|
||||
|
||||
# Start the notification thread
|
||||
self.inotify_thread = GitNotifier(self, os.getcwd())
|
||||
self.inotify_thread.start()
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt4 import QtGui
|
||||
|
||||
from cola import utils
|
||||
from cola import qtutils
|
||||
from cola.qobserver import QObserver
|
||||
from cola.settings import SettingsModel
|
||||
from cola.views import BookmarkView
|
||||
|
||||
def save_bookmark():
|
||||
model = SettingsModel()
|
||||
bookmark = os.getcwd()
|
||||
if bookmark not in model.bookmarks:
|
||||
model.add_bookmarks(bookmark)
|
||||
model.save_all_settings()
|
||||
qtutils.information("Bookmark Saved")
|
||||
|
||||
def manage_bookmarks():
|
||||
model = SettingsModel()
|
||||
view = BookmarkView(QtGui.qApp.activeWindow())
|
||||
ctl = BookmarkController(model, view)
|
||||
view.show()
|
||||
if view.exec_() == QtGui.QDialog.Accepted:
|
||||
model.save_all_settings()
|
||||
|
||||
class BookmarkController(QObserver):
|
||||
def init(self, model, view):
|
||||
self.add_observables( 'bookmarks' )
|
||||
self.add_callbacks(button_open = self.open,
|
||||
button_delete = self.delete)
|
||||
self.refresh_view()
|
||||
|
||||
def open(self):
|
||||
selection = qtutils.get_selection_list(self.view.bookmarks,
|
||||
self.model.bookmarks)
|
||||
if not selection:
|
||||
return
|
||||
for item in selection:
|
||||
utils.fork("git", "cola", item)
|
||||
|
||||
def delete(self):
|
||||
selection = qtutils.get_selection_list(self.view.bookmarks,
|
||||
self.model.bookmarks)
|
||||
if not selection:
|
||||
return
|
||||
for item in selection:
|
||||
self.model.bookmarks.remove(item)
|
||||
self.refresh_view()
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
from PyQt4.QtGui import QDialog
|
||||
|
||||
from cola import utils
|
||||
from cola import qtutils
|
||||
from cola.views import CreateBranchView
|
||||
from cola.qobserver import QObserver
|
||||
|
||||
def create_new_branch(model,parent):
|
||||
model = model.clone()
|
||||
view = CreateBranchView(parent)
|
||||
ctl = CreateBranchController(model, view)
|
||||
view.show()
|
||||
return view.exec_() == QDialog.Accepted
|
||||
|
||||
class CreateBranchController(QObserver):
|
||||
def init(self, model, view):
|
||||
self.add_observables('revision', 'local_branch')
|
||||
self.add_callbacks(branch_list = self.item_changed,
|
||||
create_button = self.create_branch,
|
||||
local_radio = self.display_model,
|
||||
remote_radio = self.display_model,
|
||||
tag_radio = self.display_model)
|
||||
self.display_model()
|
||||
|
||||
#+--------------------------------------------------------------------
|
||||
#+ Qt callbacks
|
||||
def create_branch(self):
|
||||
"""This callback is called when the "Create Branch"
|
||||
button is called."""
|
||||
|
||||
revision = self.model.get_revision()
|
||||
branch = self.model.get_local_branch()
|
||||
existing_branches = self.model.get_local_branches()
|
||||
|
||||
if not branch or not revision:
|
||||
qtutils.information(
|
||||
self.tr('Missing Data'),
|
||||
self.tr('Please provide both a branch'
|
||||
+' name and revision expression.'))
|
||||
return
|
||||
|
||||
check_branch = False
|
||||
if branch in existing_branches:
|
||||
if self.view.no_update_radio.isChecked():
|
||||
msg = self.tr("Branch '%s' already exists.")
|
||||
msg = unicode(msg) % branch
|
||||
qtutils.information( self.tr('warning'), msg )
|
||||
return
|
||||
# Whether we should prompt the user for lost commits
|
||||
commits = self.model.rev_list_range(revision, branch)
|
||||
check_branch = bool(commits)
|
||||
|
||||
if check_branch:
|
||||
msg = self.tr("Resetting '%s' to '%s' will "
|
||||
"lose the following commits:")
|
||||
lines = [ unicode(msg) % (branch, revision) ]
|
||||
|
||||
for idx, commit in enumerate(commits):
|
||||
subject = commit[1][0:min(len(commit[1]),16)]
|
||||
if len(subject) < len(commit[1]):
|
||||
subject += '...'
|
||||
lines.append('\t' + commit[0][:8]
|
||||
+'\t' + subject)
|
||||
if idx >= 5:
|
||||
skip = len(commits) - 5
|
||||
lines.append('\t(%d skipped)' % skip)
|
||||
break
|
||||
|
||||
lines.extend([
|
||||
unicode(self.tr('Recovering lost commits may not be easy.')),
|
||||
unicode(self.tr("Reset '%s'?")) % branch
|
||||
])
|
||||
|
||||
result = qtutils.question(self.view, self.tr('warning'),
|
||||
'\n'.join(lines))
|
||||
if not result:
|
||||
return
|
||||
|
||||
track = self.view.remote_radio.isChecked()
|
||||
fetch = self.view.fetch_checkbox.isChecked()
|
||||
ffwd = self.view.ffwd_only_radio.isChecked()
|
||||
reset = self.view.reset_radio.isChecked()
|
||||
|
||||
output = self.model.create_branch(branch, revision, track=track)
|
||||
qtutils.show_output(output)
|
||||
self.view.accept()
|
||||
|
||||
def item_changed(self, *rest):
|
||||
"""This callback is called when the item selection changes
|
||||
in the branch_list."""
|
||||
|
||||
qlist = self.view.branch_list
|
||||
( row, selected ) = qtutils.get_selected_row(qlist)
|
||||
if not selected: return
|
||||
|
||||
sources = self.__get_branch_sources()
|
||||
rev = sources[row]
|
||||
|
||||
# Update the model with the selection
|
||||
self.model.set_revision(rev)
|
||||
|
||||
# Only set the branch name field if we're
|
||||
# branching from a remote branch.
|
||||
if not self.view.remote_radio.isChecked():
|
||||
return
|
||||
branch = utils.basename(rev)
|
||||
if branch == 'HEAD':
|
||||
return
|
||||
self.model.set_local_branch(branch)
|
||||
|
||||
######################################################################
|
||||
|
||||
def display_model(self):
|
||||
branches = self.__get_branch_sources()
|
||||
qtutils.set_items(self.view.branch_list, branches)
|
||||
|
||||
def __get_branch_sources(self):
|
||||
"""Get the list of items for populating the branch root list."""
|
||||
if self.view.local_radio.isChecked():
|
||||
return self.model.get_local_branches()
|
||||
elif self.view.remote_radio.isChecked():
|
||||
return self.model.get_remote_branches()
|
||||
elif self.view.tag_radio.isChecked():
|
||||
return self.model.get_tags()
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
from cola import utils
|
||||
from cola import qtutils
|
||||
from cola.qobserver import QObserver
|
||||
from cola.views import MergeView
|
||||
|
||||
def abort_merge(model, parent):
|
||||
txt = parent.tr('Abort merge?\n'
|
||||
'Aborting the current merge will cause '
|
||||
'*ALL* uncommitted changes to be lost.\n\n'
|
||||
'Continue with aborting the current merge?')
|
||||
answer = qtutils.question(parent, parent.tr('Abort Merge?'), txt)
|
||||
if answer:
|
||||
model.abort_merge()
|
||||
|
||||
def local_merge(model, parent):
|
||||
model = model.clone()
|
||||
model.create(revision='', revision_list=[])
|
||||
view = MergeView(parent)
|
||||
ctl = MergeController(model, view)
|
||||
view.show()
|
||||
|
||||
class MergeController(QObserver):
|
||||
def init(self, model, view):
|
||||
# Set the current branch label
|
||||
branch = self.model.get_currentbranch()
|
||||
title = unicode(self.tr('Merge Into %s')) % branch
|
||||
self.view.label.setText(title)
|
||||
self.add_observables('revision', 'revision_list')
|
||||
self.add_callbacks(radio_local = self.radio_callback,
|
||||
radio_remote = self.radio_callback,
|
||||
radio_tag = self.radio_callback,
|
||||
revision_list = self.revision_selected,
|
||||
button_viz = self.viz_revision,
|
||||
button_merge = self.merge_revision)
|
||||
self.model.set_revision_list(self.model.get_local_branches())
|
||||
self.view.radio_local.setChecked(True)
|
||||
|
||||
def get_revision_list(self):
|
||||
if self.view.radio_local.isChecked():
|
||||
return self.model.get_local_branches()
|
||||
elif self.view.radio_remote.isChecked():
|
||||
return self.model.get_remote_branches()
|
||||
elif self.view.radio_tag.isChecked():
|
||||
return self.model.get_tags()
|
||||
return []
|
||||
|
||||
def revision_selected(self, *args):
|
||||
revlist = self.get_revision_list()
|
||||
widget = self.view.revision_list
|
||||
row, selected = qtutils.get_selected_row(widget)
|
||||
if selected and row < len(revlist):
|
||||
revision = revlist[row]
|
||||
self.model.set_revision(revision)
|
||||
|
||||
def radio_callback(self):
|
||||
revlist = self.get_revision_list()
|
||||
self.model.set_revision_list(revlist)
|
||||
|
||||
def merge_revision(self):
|
||||
revision = self.model.get_revision()
|
||||
if not revision:
|
||||
qtutils.information('No Revision Specified',
|
||||
'You must specify a revision to merge')
|
||||
return
|
||||
|
||||
no_commit = not(self.view.checkbox_commit.isChecked())
|
||||
squash = self.view.checkbox_squash.isChecked()
|
||||
msg = self.model.get_merge_message()
|
||||
qtutils.log(self.model.merge('-m'+msg,
|
||||
revision,
|
||||
strategy='recursive',
|
||||
no_commit=no_commit,
|
||||
squash=squash),
|
||||
quiet=False,
|
||||
doraise=True)
|
||||
self.view.accept()
|
||||
|
||||
def viz_revision(self):
|
||||
revision = self.model.get_revision()
|
||||
browser = self.model.get_history_browser()
|
||||
utils.fork(browser, revision)
|
||||
@@ -0,0 +1,131 @@
|
||||
import os
|
||||
from PyQt4.QtGui import QDialog
|
||||
|
||||
from cola import utils
|
||||
from cola import qtutils
|
||||
from cola.views import RemoteView
|
||||
from cola.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))
|
||||
|
||||
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))
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
from PyQt4.QtGui import QDialog
|
||||
|
||||
from cola import utils
|
||||
from cola import qtutils
|
||||
from cola import defaults
|
||||
from cola.views import CommitView
|
||||
from cola.qobserver import QObserver
|
||||
|
||||
def browse_git_branch(model, parent, branch):
|
||||
if not branch:
|
||||
return
|
||||
# Clone the model to allow opening multiple browsers
|
||||
# with different sets of data
|
||||
model = model.clone()
|
||||
model.set_currentbranch(branch)
|
||||
view = CommitView(parent)
|
||||
controller = RepoBrowserController(model, view)
|
||||
view.show()
|
||||
return view.exec_() == QDialog.Accepted
|
||||
|
||||
class RepoBrowserController(QObserver):
|
||||
def init(self, model, view):
|
||||
view.setWindowTitle('File Browser')
|
||||
self.add_signals('itemSelectionChanged()', view.commit_list,)
|
||||
self.add_actions(directory = self.action_directory_changed)
|
||||
self.add_callbacks(commit_list = self.item_changed)
|
||||
self.connect(view.commit_list,
|
||||
'itemDoubleClicked(QListWidgetItem*)',
|
||||
self.item_double_clicked)
|
||||
|
||||
# Start at the root of the tree
|
||||
model.set_directory('')
|
||||
|
||||
######################################################################
|
||||
# Actions
|
||||
def action_directory_changed(self):
|
||||
"""This is called in response to a change in the the
|
||||
model's directory."""
|
||||
self.model.init_browser_data()
|
||||
self.__display_items()
|
||||
|
||||
######################################################################
|
||||
# Qt callbacks
|
||||
def item_changed(self,*rest):
|
||||
"""This is called when the current item changes in the
|
||||
file/directory list(aka the commit_list)."""
|
||||
current = self.view.commit_list.currentRow()
|
||||
item = self.view.commit_list.item(current)
|
||||
if item is None or not item.isSelected():
|
||||
self.view.revision.setText('')
|
||||
self.view.commit_text.setText('')
|
||||
return
|
||||
directories = self.model.get_directories()
|
||||
directory_entries = self.model.get_directory_entries()
|
||||
if current < len(directories):
|
||||
# This is a directory...
|
||||
dirent = directories[current]
|
||||
if dirent != '..':
|
||||
# This is a real directory for which
|
||||
# we have child entries
|
||||
entries = directory_entries[dirent]
|
||||
else:
|
||||
# This is '..' which is a special case
|
||||
# since it doesn't really exist
|
||||
entries = []
|
||||
self.view.commit_text.setText('\n'.join(entries))
|
||||
self.view.revision.setText('')
|
||||
else:
|
||||
# This is a file entry. The current row is absolute,
|
||||
# so get a relative index by subtracting the number
|
||||
# of directory entries
|
||||
idx = current - len(directories)
|
||||
if idx >= len(self.model.get_subtree_sha1s()):
|
||||
# This can happen when changing directories
|
||||
return
|
||||
objtype, sha1, name = self.model.get_subtree_node(idx)
|
||||
|
||||
catguts = self.model.cat_file(objtype, sha1,
|
||||
with_raw_output=True)
|
||||
self.view.commit_text.setText(catguts)
|
||||
|
||||
self.view.revision.setText(sha1)
|
||||
self.view.revision.selectAll()
|
||||
|
||||
# Copy the sha1 into the clipboard
|
||||
qtutils.set_clipboard(sha1)
|
||||
|
||||
def item_double_clicked(self,*rest):
|
||||
"""This is called when an entry is double-clicked.
|
||||
This callback changes the model's directory when
|
||||
invoked on a directory item. When invoked on a file
|
||||
it allows the file to be saved."""
|
||||
|
||||
current = self.view.commit_list.currentRow()
|
||||
directories = self.model.get_directories()
|
||||
|
||||
# A file item was double-clicked.
|
||||
# Create a save-as dialog and export the file.
|
||||
if current >= len(directories):
|
||||
idx = current - len(directories)
|
||||
|
||||
objtype, sha1, name = self.model.get_subtree_node(idx)
|
||||
nameguess = os.path.join(defaults.DIRECTORY, name)
|
||||
|
||||
filename = qtutils.save_dialog(self.view, 'Save', nameguess)
|
||||
if not filename:
|
||||
return
|
||||
defaults.DIRECTORY = os.path.dirname(filename)
|
||||
contents = self.model.cat_file(objtype, sha1, raw=True)
|
||||
|
||||
utils.write(filename, contents)
|
||||
return
|
||||
|
||||
dirent = directories[current]
|
||||
curdir = self.model.get_directory()
|
||||
|
||||
# '..' is a special case--it doesn't really exist...
|
||||
if dirent == '..':
|
||||
newdir = os.path.dirname(os.path.dirname(curdir))
|
||||
if newdir == '':
|
||||
self.model.set_directory(newdir)
|
||||
else:
|
||||
self.model.set_directory(newdir + os.sep)
|
||||
else:
|
||||
self.model.set_directory(curdir + dirent)
|
||||
|
||||
######################################################################
|
||||
|
||||
def __display_items(self):
|
||||
"""This method populates the commit_list(aka item list)
|
||||
with the current directories and items. Directories are
|
||||
always listed first."""
|
||||
|
||||
self.view.commit_text.setText('')
|
||||
self.view.revision.setText('')
|
||||
|
||||
dir_icon = utils.get_icon('dir.png')
|
||||
file_icon = utils.get_icon('generic.png')
|
||||
creator = qtutils.create_listwidget_item
|
||||
|
||||
qtutils.set_items(self.view.commit_list,
|
||||
map(lambda d: creator(d, dir_icon),
|
||||
self.model.get_directories()))
|
||||
|
||||
qtutils.add_items(self.view.commit_list,
|
||||
map(lambda s: creator(s, file_icon),
|
||||
self.model.get_subtree_names()))
|
||||
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from PyQt4 import QtGui
|
||||
|
||||
from cola.observer import Observer
|
||||
from cola.qobserver import QObserver
|
||||
from cola.views import SearchView
|
||||
from cola import qtutils
|
||||
|
||||
|
||||
class SearchEngine(object):
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
self.parse = self.model.parse_rev_list
|
||||
self.init()
|
||||
def init(self):
|
||||
pass
|
||||
def get_rev_args(self):
|
||||
max = self.model.get_max_results()
|
||||
return { "max-count": max, "pretty": "oneline" }
|
||||
def get_common_args(self):
|
||||
return (self.model.get_input(), self.get_rev_args())
|
||||
def search(self):
|
||||
if not self.validate():
|
||||
return
|
||||
return self.get_results()
|
||||
def validate(self):
|
||||
return len(self.model.get_input()) > 1
|
||||
def get_results(self):
|
||||
pass
|
||||
|
||||
class RevisionSearch(SearchEngine):
|
||||
def get_results(self):
|
||||
input, args = self.get_common_args()
|
||||
expr = re.compile(input)
|
||||
revs = self.parse(self.model.rev_list(all=True, **args))
|
||||
return [ r for r in revs if expr.match(r[0]) ]
|
||||
|
||||
class RevisionRangeSearch(SearchEngine):
|
||||
def init(self):
|
||||
self.RE = re.compile(r'[^.]*\.\..*')
|
||||
def validate(self):
|
||||
return bool(self.RE.match(self.model.get_input()))
|
||||
def get_results(self):
|
||||
input, args = self.get_common_args()
|
||||
return self.parse(self.model.rev_list(input, **args))
|
||||
|
||||
class PathSearch(SearchEngine):
|
||||
def get_results(self):
|
||||
input, args = self.get_common_args()
|
||||
paths = ['--'] + input.split(':')
|
||||
return self.parse(self.model.rev_list(all=True, *paths, **args))
|
||||
|
||||
class MessageSearch(SearchEngine):
|
||||
def get_results(self):
|
||||
input, args = self.get_common_args()
|
||||
return self.parse(
|
||||
self.model.rev_list(grep=input, all=True, **args))
|
||||
|
||||
class AuthorSearch(SearchEngine):
|
||||
def get_results(self):
|
||||
input, args = self.get_common_args()
|
||||
return self.parse(
|
||||
self.model.rev_list(author=input, all=True, **args))
|
||||
|
||||
class CommitterSearch(SearchEngine):
|
||||
def get_results(self):
|
||||
input, args = self.get_common_args()
|
||||
return self.parse(
|
||||
self.model.rev_list(committer=input, all=True, **args))
|
||||
|
||||
class DiffSearch(SearchEngine):
|
||||
def get_results(self):
|
||||
input, args = self.get_common_args()
|
||||
return self.parse(
|
||||
self.model.log('-S'+input, all=True, **args))
|
||||
|
||||
class DateRangeSearch(SearchEngine):
|
||||
def validate(self):
|
||||
return True
|
||||
def get_results(self):
|
||||
args = self.get_rev_args()
|
||||
start_date = self.model.get_start_date()
|
||||
end_date = self.model.get_end_date()
|
||||
return self.parse(self.model.rev_list(date='iso',
|
||||
after=start_date,
|
||||
before=end_date,
|
||||
all=True,
|
||||
**args))
|
||||
|
||||
# Modes for this controller.
|
||||
# Note: names correspond to radio button names for convenience
|
||||
REVISION_ID = 'radio_revision'
|
||||
REVISION_RANGE = 'radio_range'
|
||||
PATH = 'radio_path'
|
||||
MESSAGE = 'radio_message'
|
||||
DIFF = 'radio_diff'
|
||||
AUTHOR = 'radio_author'
|
||||
COMMITTER = 'radio_committer'
|
||||
DATE_RANGE = 'radio_daterange'
|
||||
|
||||
# Each search type is handled by a distinct SearchEngine subclass
|
||||
SEARCH_ENGINES = {
|
||||
REVISION_ID: RevisionSearch,
|
||||
REVISION_RANGE: RevisionRangeSearch,
|
||||
PATH: PathSearch,
|
||||
MESSAGE: MessageSearch,
|
||||
DIFF: DiffSearch,
|
||||
AUTHOR: AuthorSearch,
|
||||
COMMITTER: CommitterSearch,
|
||||
DATE_RANGE: DateRangeSearch,
|
||||
}
|
||||
|
||||
class SearchController(QObserver):
|
||||
def init(self, model, view):
|
||||
self.add_observables('input',
|
||||
'max_results',
|
||||
'start_date',
|
||||
'end_date')
|
||||
self.add_actions(max_results = self.search_callback,
|
||||
start_date = self.search_callback,
|
||||
end_date = self.search_callback)
|
||||
self.add_callbacks(
|
||||
# Standard buttons
|
||||
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,
|
||||
radio_message = self.search_callback,
|
||||
radio_path = self.search_callback,
|
||||
radio_diff = self.search_callback,
|
||||
radio_author = self.search_callback,
|
||||
radio_committer = self.search_callback,
|
||||
radio_daterange = self.search_callback,
|
||||
)
|
||||
self.update_fonts()
|
||||
|
||||
def update_fonts(self):
|
||||
font = self.model.get_global_cola_fontui()
|
||||
if font:
|
||||
qfont = QtGui.QFont()
|
||||
qfont.fromString(font)
|
||||
self.view.commit_list.setFont(qfont)
|
||||
font = self.model.get_global_cola_fontdiff()
|
||||
if font:
|
||||
qfont = QtGui.QFont()
|
||||
qfont.fromString(font)
|
||||
self.view.commit_text.setFont(qfont)
|
||||
|
||||
def set_mode(self, mode):
|
||||
radio = getattr(self.view, mode)
|
||||
radio.setChecked(True)
|
||||
|
||||
def radio_to_mode(self, radio_button):
|
||||
return str(radio_button.objectName())
|
||||
|
||||
def get_mode(self):
|
||||
for name in SEARCH_ENGINES:
|
||||
radiobutton = getattr(self.view, name)
|
||||
if radiobutton.isChecked():
|
||||
return name
|
||||
|
||||
def search_callback(self, *args):
|
||||
engineclass = SEARCH_ENGINES.get(self.get_mode())
|
||||
if not engineclass:
|
||||
print ("mode: '%s' is currently unimplemented"
|
||||
% self.get_mode())
|
||||
return
|
||||
self.results = engineclass(self.model).search()
|
||||
if self.results:
|
||||
self.display_results()
|
||||
else:
|
||||
self.view.commit_list.clear()
|
||||
self.view.commit_text.setText('')
|
||||
|
||||
def browse_callback(self):
|
||||
paths = QtGui.QFileDialog.getOpenFileNames(self.view,
|
||||
self.tr("Choose Path(s)"))
|
||||
if not paths:
|
||||
return
|
||||
filepaths = []
|
||||
lenprefix = len(os.getcwd()) + 1
|
||||
for path in map(lambda x: unicode(x), paths):
|
||||
if not path.startswith(os.getcwd()):
|
||||
continue
|
||||
filepaths.append(path[lenprefix:])
|
||||
input = ':'.join(filepaths)
|
||||
self.model.set_input('')
|
||||
self.set_mode(PATH)
|
||||
self.model.set_input(input)
|
||||
|
||||
def display_results(self):
|
||||
commit_list = map(lambda x: x[1], self.results)
|
||||
self.model.set_commit_list(commit_list)
|
||||
qtutils.set_listwidget_strings(self.view.commit_list, commit_list)
|
||||
|
||||
def display_callback(self, *args):
|
||||
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.set_clipboard(revision)
|
||||
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]
|
||||
|
||||
model = model.clone()
|
||||
model.create(input='',
|
||||
max_results=500,
|
||||
start_date='',
|
||||
end_date='',
|
||||
commit_list=None)
|
||||
view = SearchView(None)
|
||||
ctl = SearchController(model, view)
|
||||
ctl.set_mode(mode)
|
||||
model.set_start_date(get_date(time.time()-(87640*7)))
|
||||
model.set_end_date(get_date(time.time()+87640))
|
||||
view.show()
|
||||
if browse:
|
||||
ctl.browse_callback()
|
||||
view.exec_()
|
||||
@@ -0,0 +1,111 @@
|
||||
import os
|
||||
|
||||
from cola import utils
|
||||
from cola import qtutils
|
||||
from cola.qobserver import QObserver
|
||||
from cola.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):
|
||||
if not qtutils.question(self.view,
|
||||
self.tr('Stash Changes?'),
|
||||
self.tr('This will stash your current '
|
||||
'changes away for later use.\n'
|
||||
'Continue?')):
|
||||
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
|
||||
(status, stdout, stderr) = self.model.stash('pop', selection,
|
||||
with_extended_output=True)
|
||||
qtutils.log(stdout + stderr,
|
||||
quiet=False,
|
||||
doraise=True)
|
||||
self.view.accept()
|
||||
|
||||
def stash_drop(self):
|
||||
selection = self.get_selected_stash()
|
||||
if not selection:
|
||||
return
|
||||
if not 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?')):
|
||||
return
|
||||
qtutils.log(self.model.stash('drop', selection),
|
||||
quiet=False,
|
||||
doraise=True)
|
||||
self.update_model()
|
||||
|
||||
def stash_clear(self):
|
||||
if not 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?')):
|
||||
return
|
||||
self.model.stash('clear'),
|
||||
self.update_model()
|
||||
@@ -0,0 +1,296 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import time
|
||||
from PyQt4.QtGui import QDialog
|
||||
from PyQt4.QtGui import QFont
|
||||
|
||||
from cola import utils
|
||||
from cola import qtutils
|
||||
from cola.model import Model
|
||||
from cola.views import ListView
|
||||
from cola.views import ComboView
|
||||
from cola.views import CommitView
|
||||
from cola.views import OptionsView
|
||||
from cola.views import LogView
|
||||
from cola.qobserver import QObserver
|
||||
|
||||
def set_diff_font(model, widget):
|
||||
if model.has_param('global_cola_fontdiff'):
|
||||
font = model.get_param('global_cola_fontdiff')
|
||||
if not font:
|
||||
return
|
||||
qf = QFont()
|
||||
qf.fromString(font)
|
||||
widget.setFont(qf)
|
||||
|
||||
def choose_from_combo(title, parent, branches):
|
||||
return ComboView(parent, title, branches).get_selected()
|
||||
|
||||
def choose_from_list(title, parent, items=[]):
|
||||
return ListView(parent, title, items).get_selected()
|
||||
|
||||
#+-------------------------------------------------------------
|
||||
def select_commits(model, parent, title, revs, summaries):
|
||||
"""Use the CommitView to select commits from a list."""
|
||||
model = model.clone()
|
||||
model.set_revisions(revs)
|
||||
model.set_summaries(summaries)
|
||||
view = CommitView(parent, title)
|
||||
ctl = SelectCommitsController(model, view)
|
||||
return ctl.select_commits()
|
||||
|
||||
class SelectCommitsController(QObserver):
|
||||
def init(self, model, view):
|
||||
set_diff_font(model, view.commit_text)
|
||||
self.connect(view.commit_list,
|
||||
'itemSelectionChanged()',
|
||||
self.commit_sha1_selected)
|
||||
|
||||
def select_commits(self):
|
||||
summaries = self.model.get_summaries()
|
||||
if not summaries:
|
||||
msg = self.tr('No commits exist in this branch.')
|
||||
qtutils.show_output(msg)
|
||||
return []
|
||||
qtutils.set_items(self.view.commit_list, summaries)
|
||||
self.view.show()
|
||||
if self.view.exec_() != QDialog.Accepted:
|
||||
return []
|
||||
revs = self.model.get_revisions()
|
||||
list_widget = self.view.commit_list
|
||||
return qtutils.get_selection_list(list_widget, revs)
|
||||
|
||||
def commit_sha1_selected(self):
|
||||
row, selected = qtutils.get_selected_row(self.view.commit_list)
|
||||
if not selected:
|
||||
self.view.commit_text.setText('')
|
||||
self.view.revision.setText('')
|
||||
return
|
||||
# Get the sha1 and put it in the revision line
|
||||
sha1 = self.model.get_revision_sha1(row)
|
||||
self.view.revision.setText(sha1)
|
||||
self.view.revision.selectAll()
|
||||
|
||||
# Lookup the sha1's commit
|
||||
commit_diff = self.model.get_commit_diff(sha1)
|
||||
self.view.commit_text.setText(commit_diff)
|
||||
|
||||
# Copy the sha1 into the clipboard
|
||||
qtutils.set_clipboard(sha1)
|
||||
|
||||
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
#+ The Options GUI Controller
|
||||
def update_options(model, parent):
|
||||
view = OptionsView(parent)
|
||||
ctl = OptionsController(model,view)
|
||||
view.show()
|
||||
return view.exec_() == QDialog.Accepted
|
||||
|
||||
class OptionsController(QObserver):
|
||||
"""ASSUMPTIONS:
|
||||
This controller assumes that the view's widgets are named
|
||||
the same as the model parameters."""
|
||||
|
||||
def init(self,model,view):
|
||||
# used for telling about interactive font changes
|
||||
self.original_model = model
|
||||
model = model.clone()
|
||||
self.add_observables('local_user_email',
|
||||
'local_user_name',
|
||||
'local_merge_summary',
|
||||
'local_merge_diffstat',
|
||||
'local_merge_verbosity',
|
||||
'local_gui_diffcontext',
|
||||
'global_user_email',
|
||||
'global_user_name',
|
||||
'global_merge_summary',
|
||||
'global_merge_diffstat',
|
||||
'global_merge_verbosity',
|
||||
'global_gui_editor',
|
||||
'global_gui_diffeditor',
|
||||
'global_gui_diffcontext',
|
||||
'global_gui_historybrowser',
|
||||
'global_cola_fontdiff_size',
|
||||
'global_cola_fontdiff',
|
||||
'global_cola_fontui_size',
|
||||
'global_cola_fontui',
|
||||
'global_cola_savewindowsettings',
|
||||
'global_cola_saveatexit')
|
||||
self.add_actions(global_cola_fontdiff_size = self.update_size)
|
||||
self.add_actions(global_cola_fontui_size = self.update_size)
|
||||
self.add_actions(global_cola_fontdiff = self.tell_parent_model)
|
||||
self.add_actions(global_cola_fontui = self.tell_parent_model)
|
||||
self.add_callbacks(save_button = self.save_settings)
|
||||
self.connect(self.view, 'rejected()', self.restore_settings)
|
||||
|
||||
self.refresh_view()
|
||||
self.backup_model = self.model.clone()
|
||||
|
||||
def refresh_view(self):
|
||||
font = self.model.get_param('global_cola_fontui')
|
||||
if font:
|
||||
size = int(font.split(',')[1])
|
||||
self.view.global_cola_fontui_size.setValue(size)
|
||||
self.model.set_global_cola_fontui_size(size)
|
||||
fontui = QFont()
|
||||
fontui.fromString(font)
|
||||
self.view.global_cola_fontui.setCurrentFont(fontui)
|
||||
|
||||
font = self.model.get_global_cola_fontdiff()
|
||||
if font:
|
||||
size = int(font.split(',')[1])
|
||||
self.view.global_cola_fontdiff_size.setValue(size)
|
||||
self.model.set_global_cola_fontdiff_size(size)
|
||||
fontdiff = QFont()
|
||||
fontdiff.fromString(font)
|
||||
self.view.global_cola_fontdiff.setCurrentFont(fontdiff)
|
||||
|
||||
self.view.local_groupbox.setTitle(unicode(self.tr('%s Repository'))
|
||||
% self.model.get_project())
|
||||
QObserver.refresh_view(self)
|
||||
|
||||
# save button
|
||||
def save_settings(self):
|
||||
params_to_save = []
|
||||
params = self.model.get_config_params()
|
||||
for param in params:
|
||||
value = self.model.get_param(param)
|
||||
backup = self.backup_model.get_param(param)
|
||||
if value != backup:
|
||||
params_to_save.append(param)
|
||||
for param in params_to_save:
|
||||
self.model.save_config_param(param)
|
||||
|
||||
self.original_model.copy_params(self.model, params_to_save)
|
||||
self.view.done(QDialog.Accepted)
|
||||
|
||||
# cancel button -> undo changes
|
||||
def restore_settings(self):
|
||||
params = self.backup_model.get_config_params()
|
||||
self.model.copy_params(self.backup_model, params)
|
||||
self.tell_parent_model()
|
||||
|
||||
def tell_parent_model(self,*rest):
|
||||
params= ('global_cola_fontdiff',
|
||||
'global_cola_fontui',
|
||||
'global_cola_fontdiff_size',
|
||||
'global_cola_fontui_size',
|
||||
'global_cola_savewindowsettings',
|
||||
'global_cola_saveatexit')
|
||||
for param in params:
|
||||
self.original_model.set_param(
|
||||
param, self.model.get_param(param))
|
||||
|
||||
def update_size(self, *rest):
|
||||
combo = self.view.global_cola_fontui
|
||||
param = str(combo.objectName())
|
||||
default = str(combo.currentFont().toString())
|
||||
self.model.apply_font_size(param, default)
|
||||
|
||||
combo = self.view.global_cola_fontdiff
|
||||
param = str(combo.objectName())
|
||||
default = str(combo.currentFont().toString())
|
||||
self.model.apply_font_size(param, default)
|
||||
|
||||
self.tell_parent_model()
|
||||
|
||||
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
#+ The Log GUI Controller
|
||||
def logger():
|
||||
model = Model(search_text = '')
|
||||
view = LogView(None)
|
||||
ctl = LogController(model,view)
|
||||
return view
|
||||
|
||||
class LogController(QObserver):
|
||||
def init(self, model, view):
|
||||
self.add_observables('search_text')
|
||||
self.add_actions(search_text = self.insta_search)
|
||||
self.add_callbacks(clear_button = self.clear,
|
||||
next_button = self.next,
|
||||
prev_button = self.prev)
|
||||
self.connect(self.view.output_text,
|
||||
'cursorPositionChanged()',
|
||||
self.cursor_position_changed)
|
||||
self.search_offset = 0
|
||||
|
||||
def insta_search(self,*rest):
|
||||
self.search_offset = 0
|
||||
txt = self.model.get_search_text().lower()
|
||||
if len(txt.strip()):
|
||||
self.next()
|
||||
else:
|
||||
cursor = self.view.output_text.textCursor()
|
||||
cursor.clearSelection()
|
||||
self.view.output_text.setTextCursor(cursor)
|
||||
|
||||
def clear(self):
|
||||
self.view.output_text.clear()
|
||||
self.search_offset = 0
|
||||
|
||||
def next(self):
|
||||
text = self.model.get_search_text().lower().strip()
|
||||
if not text:
|
||||
return
|
||||
output = str(self.view.output_text.toPlainText())
|
||||
if self.search_offset + len(text) > len(output):
|
||||
title = unicode(self.tr('%s not found')) % text
|
||||
question = unicode(self.tr("Could not find '%s'.\n"
|
||||
'Search from the beginning?')) % text
|
||||
if qtutils.question(self.view, title, question, default=False):
|
||||
self.search_offset = 0
|
||||
else:
|
||||
return
|
||||
|
||||
find_in = output[self.search_offset:].lower()
|
||||
try:
|
||||
index = find_in.index(text)
|
||||
except:
|
||||
self.search_offset = 0
|
||||
title = unicode(self.tr("%s not found")) % text
|
||||
question = unicode(self.tr("Could not find '%s'.\n"
|
||||
'Search from the beginning?')) % text
|
||||
if qtutils.question(self.view, title, default=False):
|
||||
self.next()
|
||||
return
|
||||
cursor = self.view.output_text.textCursor()
|
||||
offset = self.search_offset + index
|
||||
new_offset = offset + len(text)
|
||||
|
||||
cursor.setPosition(offset)
|
||||
cursor.setPosition(new_offset, cursor.KeepAnchor)
|
||||
|
||||
self.view.output_text.setTextCursor(cursor)
|
||||
self.search_offset = new_offset
|
||||
|
||||
def prev(self):
|
||||
text = self.model.get_search_text().lower().strip()
|
||||
if not text:
|
||||
return
|
||||
output = str(self.view.output_text.toPlainText())
|
||||
if self.search_offset == 0:
|
||||
self.search_offset = len(output)
|
||||
|
||||
find_in = output[:self.search_offset].lower()
|
||||
try:
|
||||
offset = find_in.rindex(text)
|
||||
except:
|
||||
self.search_offset = 0
|
||||
title = unicode(self.tr('%s not found')) % text
|
||||
question = unicode(self.tr("Could not find '%s'.\n"
|
||||
'Search from the end?')) % text
|
||||
if qtutils.question(self.view, title, question):
|
||||
self.prev()
|
||||
return
|
||||
cursor = self.view.output_text.textCursor()
|
||||
new_offset = offset + len(text)
|
||||
|
||||
cursor.setPosition(offset)
|
||||
cursor.setPosition(new_offset, cursor.KeepAnchor)
|
||||
|
||||
self.view.output_text.setTextCursor(cursor)
|
||||
self.search_offset = offset
|
||||
|
||||
def cursor_position_changed(self):
|
||||
cursor = self.view.output_text.textCursor()
|
||||
self.search_offset = cursor.selectionStart()
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
import Common
|
||||
def build(bld):
|
||||
# Compile the cola support modules
|
||||
pyqt = bld.create_obj('py')
|
||||
pyqt.inst_var = 'COLA_CONTROLLERS'
|
||||
pyqt.find_sources_in_dirs('.')
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
from PyQt4.QtCore import QEvent
|
||||
from cola import version
|
||||
VERSION = version.VERSION
|
||||
DIRECTORY = os.getcwd()
|
||||
|
||||
WIDTH = 780
|
||||
HEIGHT = 600
|
||||
|
||||
X = 262
|
||||
Y = 254
|
||||
|
||||
INOTIFY_EVENT = QEvent.User + 0
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright(C) 2007, David Aguilar <davvid@gmail.com>
|
||||
import os
|
||||
import sys
|
||||
import optparse
|
||||
from os.path import join
|
||||
from os.path import dirname
|
||||
from os.path import realpath
|
||||
|
||||
def setup_environment():
|
||||
"""Prepends sys.path with cola's module path."""
|
||||
# ex: sys.path = [ /usr/share, ... ]
|
||||
# from cola import utils
|
||||
# implies: utils = /usr/share/cola/utils.py
|
||||
cola = realpath(sys.argv[0])
|
||||
bin = dirname(cola)
|
||||
prefix = dirname(bin)
|
||||
share = join(prefix,'share')
|
||||
sys.path.insert(0, share)
|
||||
|
||||
def main():
|
||||
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 cola version',
|
||||
dest='version',
|
||||
default=False,
|
||||
action='store_true')
|
||||
opts, args = parser.parse_args()
|
||||
|
||||
# allow "git cola /path/to/repo"
|
||||
if args:
|
||||
os.chdir(os.path.realpath(args[0]))
|
||||
|
||||
# This sets up sys.path so that the cola modules can be found.
|
||||
setup_environment()
|
||||
|
||||
if opts.version:
|
||||
from cola.version import VERSION
|
||||
print "cola version", VERSION
|
||||
sys.exit(0)
|
||||
|
||||
# allow "git cola /repo/path-1 .. /repo/path-N
|
||||
from cola.models import Model
|
||||
from cola import utils
|
||||
from cola.views import View
|
||||
from cola.controllers import Controller
|
||||
|
||||
if args[1:]:
|
||||
for repo in [ os.path.realpath(r) for r in args[1:] ]:
|
||||
utils.fork("git", "cola", repo)
|
||||
|
||||
# load the model right away so that we can bail out when
|
||||
# no git repo exists
|
||||
model = Model()
|
||||
|
||||
# PyQt4
|
||||
try:
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4 import QtGui
|
||||
except ImportError:
|
||||
print "Sorry, you do not seem to have PyQt4 installed."
|
||||
print "Please install it before using cola."
|
||||
print "e.g.: sudo apt-get install python-qt4"
|
||||
sys.exit(-1)
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
app.setWindowIcon(QtGui.QIcon( utils.get_icon('git.png')) )
|
||||
locale = str(QtCore.QLocale().system().name())
|
||||
qmfile = utils.get_qm_for_locale( locale )
|
||||
if os.path.exists(qmfile):
|
||||
translator = QtCore.QTranslator()
|
||||
translator.load(qmfile)
|
||||
app.installTranslator(translator)
|
||||
# simple mvc
|
||||
view = View(app.activeWindow())
|
||||
ctl = Controller(model, view)
|
||||
view.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
"""
|
||||
This module is from http://gitorious.org/projects/git-python/
|
||||
and is included here for convenience.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
def dashify(string):
|
||||
return string.replace('_', '-')
|
||||
|
||||
class MethodMissingMixin(object):
|
||||
"""
|
||||
A Mixin' to implement the 'method_missing' Ruby-like protocol.
|
||||
|
||||
This was `taken from a blog post <http://blog.iffy.us/?p=43>`_
|
||||
"""
|
||||
def __getattr__(self, attr):
|
||||
class MethodMissing(object):
|
||||
def __init__(self, wrapped, method):
|
||||
self.__wrapped__ = wrapped
|
||||
self.__method__ = method
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.__wrapped__.method_missing(self.__method__, *args, **kwargs)
|
||||
return MethodMissing(self, attr)
|
||||
|
||||
def method_missing(self, *args, **kwargs):
|
||||
""" This method should be overridden in the derived class. """
|
||||
raise NotImplementedError(str(self.__wrapped__) + " 'method_missing' method has not been implemented.")
|
||||
|
||||
|
||||
# Enables debugging of GitPython's git commands
|
||||
GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
|
||||
|
||||
execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output',
|
||||
'with_exceptions', 'with_raw_output')
|
||||
|
||||
class Git(MethodMissingMixin):
|
||||
"""
|
||||
The Git class manages communication with the Git binary
|
||||
"""
|
||||
def __init__(self, git_dir):
|
||||
super(Git, self).__init__()
|
||||
self.git_dir = git_dir
|
||||
|
||||
@property
|
||||
def get_dir(self):
|
||||
return self.git_dir
|
||||
|
||||
def execute(self, command,
|
||||
istream=None,
|
||||
with_keep_cwd=False,
|
||||
with_extended_output=False,
|
||||
with_exceptions=True,
|
||||
with_raw_output=False,
|
||||
):
|
||||
"""
|
||||
Handles executing the command on the shell and consumes and returns
|
||||
the returned information (stdout)
|
||||
|
||||
``command``
|
||||
The command argument list to execute
|
||||
|
||||
``istream``
|
||||
Standard input filehandle passed to subprocess.Popen.
|
||||
|
||||
``with_keep_cwd``
|
||||
Whether to use the current working directory from os.getcwd().
|
||||
GitPython uses get_work_tree() as its working directory by
|
||||
default and get_git_dir() for bare repositories.
|
||||
|
||||
``with_extended_output``
|
||||
Whether to return a (status, stdout, stderr) tuple.
|
||||
|
||||
``with_exceptions``
|
||||
Whether to raise an exception when git returns a non-zero status.
|
||||
|
||||
``with_raw_output``
|
||||
Whether to avoid stripping off trailing whitespace.
|
||||
|
||||
Returns
|
||||
str(output) # extended_output = False (Default)
|
||||
tuple(int(status), str(output)) # extended_output = True
|
||||
"""
|
||||
|
||||
if GIT_PYTHON_TRACE and not GIT_PYTHON_TRACE == 'full':
|
||||
print ' '.join(command)
|
||||
|
||||
# Allow the user to have the command executed in their working dir.
|
||||
if with_keep_cwd or self.git_dir is None:
|
||||
cwd = os.getcwd()
|
||||
else:
|
||||
cwd=self.git_dir
|
||||
|
||||
# Start the process
|
||||
proc = subprocess.Popen(command,
|
||||
cwd=cwd,
|
||||
stdin=istream,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE
|
||||
)
|
||||
|
||||
# Wait for the process to return
|
||||
try:
|
||||
stdout_value = proc.stdout.read()
|
||||
stderr_value = proc.stderr.read()
|
||||
status = proc.wait()
|
||||
finally:
|
||||
proc.stdout.close()
|
||||
proc.stderr.close()
|
||||
|
||||
# Strip off trailing whitespace by default
|
||||
if not with_raw_output:
|
||||
stdout_value = stdout_value.rstrip()
|
||||
stderr_value = stderr_value.rstrip()
|
||||
|
||||
if with_exceptions and status != 0:
|
||||
raise GitCommandError(command, status, stderr_value)
|
||||
|
||||
if GIT_PYTHON_TRACE == 'full':
|
||||
if stderr_value:
|
||||
print "%s -> %d: '%s' !! '%s'" % (command, status, stdout_value, stderr_value)
|
||||
elif stdout_value:
|
||||
print "%s -> %d: '%s'" % (command, status, stdout_value)
|
||||
else:
|
||||
print "%s -> %d" % (command, status)
|
||||
|
||||
# Allow access to the command's status code
|
||||
if with_extended_output:
|
||||
return (status, stdout_value, stderr_value)
|
||||
else:
|
||||
return stdout_value
|
||||
|
||||
def transform_kwargs(self, **kwargs):
|
||||
"""
|
||||
Transforms Python style kwargs into git command line options.
|
||||
"""
|
||||
args = []
|
||||
for k, v in kwargs.items():
|
||||
if len(k) == 1:
|
||||
if v is True:
|
||||
args.append("-%s" % k)
|
||||
elif type(v) is not bool:
|
||||
args.append("-%s%s" % (k, v))
|
||||
else:
|
||||
if v is True:
|
||||
args.append("--%s" % dashify(k))
|
||||
elif type(v) is not bool:
|
||||
args.append("--%s=%s" % (dashify(k), v))
|
||||
return args
|
||||
|
||||
def method_missing(self, method, *args, **kwargs):
|
||||
"""
|
||||
Run the given git command with the specified arguments and return
|
||||
the result as a String
|
||||
|
||||
``method``
|
||||
is the command
|
||||
|
||||
``args``
|
||||
is the list of arguments
|
||||
|
||||
``kwargs``
|
||||
is a dict of keyword arguments.
|
||||
This function accepts the same optional keyword arguments
|
||||
as execute().
|
||||
|
||||
Examples
|
||||
git.rev_list('master', max_count=10, header=True)
|
||||
|
||||
Returns
|
||||
Same as execute()
|
||||
"""
|
||||
|
||||
# Handle optional arguments prior to calling transform_kwargs
|
||||
# otherwise these'll end up in args, which is bad.
|
||||
_kwargs = {}
|
||||
for kwarg in execute_kwargs:
|
||||
try:
|
||||
_kwargs[kwarg] = kwargs.pop(kwarg)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Prepare the argument list
|
||||
opt_args = self.transform_kwargs(**kwargs)
|
||||
ext_args = map(str, args)
|
||||
args = opt_args + ext_args
|
||||
|
||||
call = ["git", dashify(method)]
|
||||
call.extend(args)
|
||||
|
||||
return self.execute(call, **_kwargs)
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import time
|
||||
from PyQt4.QtCore import QCoreApplication
|
||||
from PyQt4.QtCore import QThread
|
||||
from PyQt4.QtCore import QEvent
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
from pyinotify import ProcessEvent
|
||||
from pyinotify import WatchManager, Notifier, EventsCodes
|
||||
|
||||
from models import GitCola
|
||||
import defaults
|
||||
|
||||
class FileSysEvent(ProcessEvent):
|
||||
def __init__(self, parent):
|
||||
ProcessEvent.__init__(self)
|
||||
self.parent = parent
|
||||
self.last_event_time = time.time()
|
||||
def process_default(self, event):
|
||||
# Prevent notificaiton floods
|
||||
if time.time() - self.last_event_time > 1.0:
|
||||
self.parent.notify()
|
||||
self.last_event_time = time.time()
|
||||
|
||||
class GitNotifier(QThread):
|
||||
def __init__(self, receiver, path):
|
||||
QThread.__init__(self)
|
||||
self.git = GitCola()
|
||||
self.receiver = receiver
|
||||
self.path = path
|
||||
self.abort = False
|
||||
self.dirs_seen = {}
|
||||
self.mask = (EventsCodes.IN_CREATE |
|
||||
EventsCodes.IN_DELETE |
|
||||
EventsCodes.IN_MODIFY |
|
||||
EventsCodes.IN_MOVED_TO)
|
||||
|
||||
def notify(self):
|
||||
if not self.abort:
|
||||
event_type = QEvent.Type(defaults.INOTIFY_EVENT)
|
||||
event = QEvent(event_type)
|
||||
QCoreApplication.postEvent(self.receiver, event)
|
||||
|
||||
def watch_directory(self, directory):
|
||||
directory = os.path.realpath(directory)
|
||||
if directory not in self.dirs_seen:
|
||||
self.wm.add_watch(directory, self.mask)
|
||||
self.dirs_seen[directory] = True
|
||||
|
||||
def run(self):
|
||||
# Only capture those events that git cares about
|
||||
self.wm = WatchManager()
|
||||
notifier = Notifier(self.wm, FileSysEvent(self))
|
||||
self.notifier = notifier
|
||||
dirs_seen = {}
|
||||
added_flag = False
|
||||
while not self.abort:
|
||||
if not added_flag:
|
||||
self.watch_directory(self.path)
|
||||
# Register files/directories known to git
|
||||
for file in self.git.ls_files().splitlines():
|
||||
directory = os.path.dirname(file)
|
||||
self.watch_directory(directory)
|
||||
added_flag = True
|
||||
notifier.process_events()
|
||||
if notifier.check_events(timeout=250):
|
||||
notifier.read_events()
|
||||
notifier.stop()
|
||||
+368
@@ -0,0 +1,368 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import imp
|
||||
from cStringIO import StringIO
|
||||
from types import DictType
|
||||
from types import ListType
|
||||
from types import TupleType
|
||||
from types import StringTypes
|
||||
from types import BooleanType
|
||||
from types import IntType
|
||||
from types import LongType
|
||||
from types import FloatType
|
||||
from types import ComplexType
|
||||
from types import InstanceType
|
||||
|
||||
class Observable(object):
|
||||
"""Handles subject/observer notifications."""
|
||||
def __init__(self,*args,**kwargs):
|
||||
self.__observers = []
|
||||
self.__notify = True
|
||||
def get_notify(self):
|
||||
return self.__notify
|
||||
def set_notify(self, notify=True):
|
||||
self.__notify = notify
|
||||
def add_observer(self, observer):
|
||||
if observer not in self.__observers:
|
||||
self.__observers.append(observer)
|
||||
def remove_observer(self, observer):
|
||||
if observer in self.__observers:
|
||||
self.__observers.remove(observer)
|
||||
def notify_observers(self, *param):
|
||||
if not self.__notify: return
|
||||
for observer in self.__observers:
|
||||
observer.notify(*param)
|
||||
|
||||
class Model(Observable):
|
||||
"""Creates a generic model object with params specified
|
||||
as a name:value dictionary.
|
||||
|
||||
get_name() and set_name(value) are created automatically
|
||||
for any of the parameters specified in the **kwargs"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Observable.__init__(self)
|
||||
self.__params = []
|
||||
# For meta-programmability
|
||||
self.from_dict(kwargs)
|
||||
self.init()
|
||||
|
||||
# for subclasses
|
||||
def init(self):
|
||||
pass
|
||||
|
||||
def create(self,**kwargs):
|
||||
return self.from_dict(kwargs)
|
||||
|
||||
def get_param_names(self):
|
||||
return tuple(self.__params)
|
||||
|
||||
def notify_all(self):
|
||||
self.notify_observers(*self.get_param_names())
|
||||
|
||||
def clone(self, *args, **kwargs):
|
||||
return self.__class__(*args, **kwargs).from_dict(self.to_dict())
|
||||
|
||||
def has_param(self,param):
|
||||
return param in self.__params
|
||||
|
||||
def get_param(self,param):
|
||||
return getattr(self, param)
|
||||
|
||||
def __getattr__(self, param):
|
||||
"""Provides automatic get/set/add/append methods."""
|
||||
|
||||
# Base case: we actually have this param
|
||||
if param in self.__dict__:
|
||||
return getattr(self, param)
|
||||
|
||||
# Check for the translated variant of the param
|
||||
realparam = self.__translate(param, sep='')
|
||||
if realparam in self.__dict__:
|
||||
return getattr(self, realparam)
|
||||
|
||||
if realparam.startswith("get"):
|
||||
param = self.__translate(param, "get")
|
||||
return lambda: getattr(self, param)
|
||||
|
||||
elif realparam.startswith("set"):
|
||||
param = self.__translate(param, "set")
|
||||
return lambda v: self.set_param(param, v,
|
||||
check_params=True)
|
||||
|
||||
elif (realparam.startswith("add") or realparam.startswith("append")):
|
||||
if realparam.startswith("add"):
|
||||
param = self.__translate(realparam, "add")
|
||||
else:
|
||||
param = self.__translate(realparam, "append")
|
||||
|
||||
def array_append(*values):
|
||||
array = getattr(self, param)
|
||||
if array is None:
|
||||
classnm = self.__class__.__name__
|
||||
errmsg = ("%s object has no array named '%s'"
|
||||
%( classnm, param ))
|
||||
raise AttributeError(errmsg)
|
||||
else:
|
||||
array.extend(values)
|
||||
# Cache the function definition
|
||||
setattr(self, realparam, array_append)
|
||||
return array_append
|
||||
|
||||
errmsg = ("%s object has no parameter '%s'"
|
||||
% (self.__class__.__name__, param))
|
||||
|
||||
raise AttributeError(errmsg)
|
||||
|
||||
def set_param(self, param, value, notify=True, check_params=False):
|
||||
"""Set param with optional notification and validity checks."""
|
||||
|
||||
param = param.lower()
|
||||
if check_params and param not in self.__params:
|
||||
raise Exception("Parameter '%s' not available for %s"
|
||||
% (param, self.__class__.__name__))
|
||||
elif param not in self.__params:
|
||||
self.__params.append(param)
|
||||
|
||||
setattr(self, param, value)
|
||||
if notify:
|
||||
self.notify_observers(param)
|
||||
|
||||
def copy_params(self, model, params=None):
|
||||
if params is None:
|
||||
params = self.get_param_names()
|
||||
for param in params:
|
||||
self.set_param(param, model.get_param(param))
|
||||
|
||||
def __translate(self, param, prefix='', sep='_'):
|
||||
"""Translates an param name from the external name
|
||||
used in methods to those used internally. The default
|
||||
settings strip off '_' so that both get_foo() and getFoo()
|
||||
are valid incantations."""
|
||||
return param[len(prefix):].lstrip(sep).lower()
|
||||
|
||||
def save(self, filename):
|
||||
if not has_json():
|
||||
return
|
||||
import simplejson
|
||||
file = open(filename, 'w')
|
||||
simplejson.dump(self.to_dict(), file, indent=4)
|
||||
file.close()
|
||||
|
||||
def load(self, filename):
|
||||
if not has_json():
|
||||
return
|
||||
import simplejson
|
||||
file = open(filename, 'r')
|
||||
ddict = simplejson.load(file)
|
||||
file.close()
|
||||
if "__class__" in ddict:
|
||||
# load params in-place.
|
||||
del ddict["__class__"]
|
||||
return self.from_dict(ddict)
|
||||
|
||||
@staticmethod
|
||||
def instance(filename):
|
||||
if not has_json():
|
||||
return
|
||||
import simplejson
|
||||
file = open(filename, 'r')
|
||||
ddict = simplejson.load(file)
|
||||
file.close()
|
||||
if "__class__" in ddict:
|
||||
cls = Model.str_to_class(ddict["__class__"])
|
||||
del ddict["__class__"]
|
||||
return cls().from_dict(ddict)
|
||||
else:
|
||||
return Model().from_dict(ddict)
|
||||
|
||||
def from_dict(self, source_dict):
|
||||
"""Import a complex model from a dictionary.
|
||||
We store class information in the __class__ variable.
|
||||
If it looks like a duck, it's a duck."""
|
||||
|
||||
if "__class__" in source_dict:
|
||||
clsstr = source_dict["__class__"]
|
||||
del source_dict["__class__"]
|
||||
cls = Model.str_to_class(clsstr)
|
||||
return cls().from_dict(source_dict)
|
||||
|
||||
# Not initiating a clone: load parameters in-place
|
||||
for param, val in source_dict.iteritems():
|
||||
self.set_param(param,
|
||||
self.__obj_from_value(val),
|
||||
notify=False)
|
||||
self.__params.sort()
|
||||
return self
|
||||
|
||||
def __obj_from_value(self, val):
|
||||
# Atoms
|
||||
if is_atom(val):
|
||||
return val
|
||||
|
||||
# Possibly nested lists
|
||||
elif is_list(val):
|
||||
return [ self.__obj_from_value(v) for v in val ]
|
||||
|
||||
elif is_dict(val):
|
||||
# A param that maps to a Model-object
|
||||
if "__class__" in val:
|
||||
clsstr = val["__class__"]
|
||||
cls = Model.str_to_class(clsstr)
|
||||
del val["__class__"]
|
||||
return cls().from_dict(val)
|
||||
newdict = {}
|
||||
for k, v in val.iteritems():
|
||||
newdict[k] = self.__obj_from_value(v)
|
||||
return newdict
|
||||
|
||||
# All others
|
||||
return val
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Exports a model to a dictionary.
|
||||
This simplifies serialization.
|
||||
"""
|
||||
params = {"__class__": Model.class_to_str(self)}
|
||||
for param in self.__params:
|
||||
params[param] = self.__obj_to_value(getattr(self, param))
|
||||
return params
|
||||
|
||||
def __obj_to_value(self, item):
|
||||
if is_atom(item):
|
||||
return item
|
||||
|
||||
elif is_list(item):
|
||||
newlist = [ self.__obj_to_value(i) for i in item ]
|
||||
return newlist
|
||||
|
||||
elif is_dict(item):
|
||||
newdict = {}
|
||||
for k,v in item.iteritems():
|
||||
newdict[k] = self.__obj_to_value(v)
|
||||
return newdict
|
||||
|
||||
elif is_instance(item):
|
||||
return item.to_dict()
|
||||
|
||||
else:
|
||||
raise NotImplementedError("Unknown type:" + str(type(item)))
|
||||
|
||||
__INDENT__ = 0
|
||||
__PREINDENT__ = True
|
||||
__STRSTACK__ = []
|
||||
|
||||
@staticmethod
|
||||
def INDENT(i=0):
|
||||
Model.__INDENT__ += i
|
||||
return '\t' * Model.__INDENT__
|
||||
|
||||
def __str__(self):
|
||||
"""A convenient, recursively-defined stringification method."""
|
||||
|
||||
# This avoid infinite recursion on cyclical structures
|
||||
if self in Model.__STRSTACK__:
|
||||
return 'REFERENCE' # TODO: implement references?
|
||||
else:
|
||||
Model.__STRSTACK__.append(self)
|
||||
|
||||
io = StringIO()
|
||||
|
||||
if Model.__PREINDENT__:
|
||||
io.write(Model.INDENT())
|
||||
|
||||
io.write(self.__class__.__name__ + '(')
|
||||
|
||||
Model.INDENT(1)
|
||||
|
||||
for param in self.__params:
|
||||
if param.startswith('_'):
|
||||
continue
|
||||
io.write('\n')
|
||||
|
||||
inner = Model.INDENT() + param + " = "
|
||||
value = getattr(self, param)
|
||||
|
||||
if type(value) == ListType:
|
||||
indent = Model.INDENT(1)
|
||||
io.write(inner + "[\n")
|
||||
for val in value:
|
||||
if is_model(val):
|
||||
io.write(str(val)+'\n')
|
||||
else:
|
||||
io.write(indent)
|
||||
io.write(str(val))
|
||||
io.write(",\n")
|
||||
|
||||
io.write(Model.INDENT(-1))
|
||||
io.write("],")
|
||||
else:
|
||||
Model.__PREINDENT__ = False
|
||||
io.write(inner)
|
||||
io.write(str(value))
|
||||
io.write(',')
|
||||
Model.__PREINDENT__ = True
|
||||
|
||||
io.write('\n' + Model.INDENT(-1) + ')')
|
||||
value = io.getvalue()
|
||||
io.close()
|
||||
|
||||
Model.__STRSTACK__.remove(self)
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def str_to_class(clstr):
|
||||
items = clstr.split('.')
|
||||
modules = items[:-1]
|
||||
classname = items[-1]
|
||||
path = None
|
||||
module = None
|
||||
for mod in modules:
|
||||
search = imp.find_module(mod, path)
|
||||
try:
|
||||
module = imp.load_module(mod, *search)
|
||||
if hasattr(module, "__path__"):
|
||||
path = module.__path__
|
||||
finally:
|
||||
if search and search[0]:
|
||||
search[0].close()
|
||||
if module:
|
||||
return getattr(module, classname)
|
||||
else:
|
||||
raise Exception("No class found for: %s" % clstr)
|
||||
|
||||
@staticmethod
|
||||
def class_to_str(instance):
|
||||
modname = instance.__module__
|
||||
classname = instance.__class__.__name__
|
||||
return "%s.%s" % (modname, classname)
|
||||
|
||||
|
||||
#############################################################################
|
||||
def has_json():
|
||||
try:
|
||||
import simplejson
|
||||
return True
|
||||
except ImportError:
|
||||
print "Unable to import simplejson." % action
|
||||
print "You do not have simplejson installed."
|
||||
print "try: sudo apt-get install simplejson"
|
||||
return False
|
||||
|
||||
#############################################################################
|
||||
def is_model(item):
|
||||
return issubclass(item.__class__, Model)
|
||||
def is_dict(item):
|
||||
return type(item) is DictType
|
||||
def is_list(item):
|
||||
return type(item) is ListType or type(item) is TupleType
|
||||
def is_atom(item):
|
||||
return(type(item) in StringTypes
|
||||
or type(item) is BooleanType
|
||||
or type(item) is IntType
|
||||
or type(item) is LongType
|
||||
or type(item) is FloatType
|
||||
or type(item) is ComplexType)
|
||||
def is_instance(item):
|
||||
return(is_model(item) or type(item) is InstanceType)
|
||||
+958
@@ -0,0 +1,958 @@
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import time
|
||||
import subprocess
|
||||
from cStringIO import StringIO
|
||||
|
||||
from cola import git
|
||||
from cola import utils
|
||||
from cola 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 GitCola(git.Git):
|
||||
"""GitPython throws exceptions by default.
|
||||
We suppress exceptions in favor of return values.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._git_dir = None
|
||||
self._work_tree = None
|
||||
git_dir = self.get_git_dir()
|
||||
work_tree = self.get_work_tree()
|
||||
if work_tree:
|
||||
os.chdir(work_tree)
|
||||
else:
|
||||
print "Sorry, git-cola requires a work tree"
|
||||
sys.exit(-1)
|
||||
git.Git.__init__(self, work_tree)
|
||||
def execute(*args, **kwargs):
|
||||
kwargs['with_exceptions'] = False
|
||||
return git.Git.execute(*args, **kwargs)
|
||||
def get_work_tree(self):
|
||||
if not self._work_tree:
|
||||
self._work_tree = os.getenv('GIT_WORK_TREE')
|
||||
if not self._work_tree or not os.path.isdir(self._work_tree):
|
||||
self._work_tree = os.path.abspath(
|
||||
os.path.join(self._git_dir, '..'))
|
||||
return self._work_tree
|
||||
def get_git_dir(self):
|
||||
if not self._git_dir:
|
||||
self._git_dir = os.getenv('GIT_DIR')
|
||||
if self._git_dir and self._is_git_dir(self._git_dir):
|
||||
return self._git_dir
|
||||
curpath = os.getcwd()
|
||||
while curpath:
|
||||
if self._is_git_dir(curpath):
|
||||
self._git_dir = curpath
|
||||
break
|
||||
gitpath = os.path.join(curpath, '.git')
|
||||
if self._is_git_dir(gitpath):
|
||||
self._git_dir = gitpath
|
||||
break
|
||||
curpath, dummy = os.path.split(curpath)
|
||||
if not dummy:
|
||||
break
|
||||
return self._git_dir
|
||||
|
||||
def _is_git_dir(self, d):
|
||||
""" This is taken from the git setup.c:is_git_directory
|
||||
function."""
|
||||
if (os.path.isdir(d)
|
||||
and os.path.isdir(os.path.join(d, 'objects'))
|
||||
and os.path.isdir(os.path.join(d, 'refs'))):
|
||||
headref = os.path.join(d, 'HEAD')
|
||||
return (os.path.isfile(headref)
|
||||
or (os.path.islink(headref)
|
||||
and os.readlink(headref).startswith('refs')))
|
||||
return False
|
||||
|
||||
class Model(model.Model):
|
||||
"""Provides a friendly wrapper for doing commit git operations."""
|
||||
|
||||
def init(self):
|
||||
"""Reads git repository settings and sets several methods
|
||||
so that they refer to the git module. This object is
|
||||
encapsulates cola's interaction with git."""
|
||||
|
||||
# chdir to the root of the git tree.
|
||||
# This keeps paths relative.
|
||||
self.git = GitCola()
|
||||
|
||||
# Read git config
|
||||
self.__init_config_data()
|
||||
|
||||
# Import all git commands from git.py
|
||||
for cmd in GIT_COMMANDS:
|
||||
setattr(self, cmd, getattr(self.git, cmd))
|
||||
|
||||
self.create(
|
||||
#####################################################
|
||||
# Used in various places
|
||||
currentbranch = '',
|
||||
remotes = [],
|
||||
remotename = '',
|
||||
local_branch = '',
|
||||
remote_branch = '',
|
||||
search_text = '',
|
||||
git_version = self.git.version(),
|
||||
|
||||
#####################################################
|
||||
# Used primarily by the main UI
|
||||
project = os.path.basename(os.getcwd()),
|
||||
commitmsg = '',
|
||||
modified = [],
|
||||
staged = [],
|
||||
unstaged = [],
|
||||
untracked = [],
|
||||
window_geom = utils.parse_geom(self.get_global_cola_geometry()),
|
||||
|
||||
#####################################################
|
||||
# Used by the create branch dialog
|
||||
revision = '',
|
||||
local_branches = [],
|
||||
remote_branches = [],
|
||||
tags = [],
|
||||
|
||||
#####################################################
|
||||
# Used by the commit/repo browser
|
||||
directory = '',
|
||||
revisions = [],
|
||||
summaries = [],
|
||||
|
||||
# These are parallel lists
|
||||
types = [],
|
||||
sha1s = [],
|
||||
names = [],
|
||||
|
||||
# All items below here are re-calculated in
|
||||
# init_browser_data()
|
||||
directories = [],
|
||||
directory_entries = {},
|
||||
|
||||
# These are also parallel lists
|
||||
subtree_types = [],
|
||||
subtree_sha1s = [],
|
||||
subtree_names = [],
|
||||
)
|
||||
|
||||
def __init_config_data(self):
|
||||
"""Reads git config --list and creates parameters
|
||||
for each setting."""
|
||||
# These parameters are saved in .gitconfig,
|
||||
# so ideally these should be as short as possible.
|
||||
|
||||
# config items that are controllable globally
|
||||
# and per-repository
|
||||
self.__local_and_global_defaults = {
|
||||
'user_name': '',
|
||||
'user_email': '',
|
||||
'merge_summary': False,
|
||||
'merge_diffstat': True,
|
||||
'merge_verbosity': 2,
|
||||
'gui_diffcontext': 3,
|
||||
'gui_pruneduringfetch': False,
|
||||
}
|
||||
# config items that are purely git config --global settings
|
||||
self.__global_defaults = {
|
||||
'cola_geometry':'',
|
||||
'cola_fontui': '',
|
||||
'cola_fontui_size':12,
|
||||
'cola_fontdiff': '',
|
||||
'cola_fontdiff_size':12,
|
||||
'cola_savewindowsettings': False,
|
||||
'cola_saveatexit': False,
|
||||
'gui_editor': 'gvim',
|
||||
'gui_diffeditor': 'xxdiff',
|
||||
'gui_historybrowser': 'gitk',
|
||||
}
|
||||
|
||||
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)
|
||||
for k,v in global_dict.iteritems():
|
||||
self.set_param('global_'+k, v)
|
||||
if k not in local_dict:
|
||||
local_dict[k]=v
|
||||
self.set_param('local_'+k, v)
|
||||
|
||||
# Bootstrap the internal font*_size variables
|
||||
for param in ('global_cola_fontui', 'global_cola_fontdiff'):
|
||||
if hasattr(self, param):
|
||||
font = self.get_param(param)
|
||||
if font:
|
||||
size = int(font.split(',')[1])
|
||||
self.set_param(param+'_size', size)
|
||||
param = param[len('global_'):]
|
||||
global_dict[param] = font
|
||||
global_dict[param+'_size'] = size
|
||||
|
||||
# Load defaults for all undefined items
|
||||
local_and_global_defaults = self.__local_and_global_defaults
|
||||
for k,v in local_and_global_defaults.iteritems():
|
||||
if k not in local_dict:
|
||||
self.set_param('local_'+k, v)
|
||||
if k not in global_dict:
|
||||
self.set_param('global_'+k, v)
|
||||
|
||||
global_defaults = self.__global_defaults
|
||||
for k,v in global_defaults.iteritems():
|
||||
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
|
||||
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':
|
||||
self.diff_context = value
|
||||
if param.startswith('local_'):
|
||||
param = param[len('local_'):]
|
||||
is_local = True
|
||||
elif param.startswith('global_'):
|
||||
param = param[len('global_'):]
|
||||
is_local = False
|
||||
else:
|
||||
raise Exception("Invalid param '%s' passed to " % param
|
||||
+'save_config_param()')
|
||||
param = param.replace('_', '.') # model -> git
|
||||
return self.config_set(param, value, local=is_local)
|
||||
|
||||
def init_browser_data(self):
|
||||
"""This scans over self.(names, sha1s, types) to generate
|
||||
directories, directory_entries, and subtree_*"""
|
||||
|
||||
# Collect data for the model
|
||||
if not self.get_currentbranch(): return
|
||||
|
||||
self.subtree_types = []
|
||||
self.subtree_sha1s = []
|
||||
self.subtree_names = []
|
||||
self.directories = []
|
||||
self.directory_entries = {}
|
||||
|
||||
# Lookup the tree info
|
||||
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 ))
|
||||
self.set_names(map( lambda(x): x[3], tree_info ))
|
||||
|
||||
if self.directory: self.directories.append('..')
|
||||
|
||||
dir_entries = self.directory_entries
|
||||
dir_regex = re.compile('([^/]+)/')
|
||||
dirs_seen = {}
|
||||
subdirs_seen = {}
|
||||
|
||||
for idx, name in enumerate(self.names):
|
||||
if not name.startswith(self.directory):
|
||||
continue
|
||||
name = name[ len(self.directory): ]
|
||||
if name.count('/'):
|
||||
# This is a directory...
|
||||
match = dir_regex.match(name)
|
||||
if not match:
|
||||
continue
|
||||
dirent = match.group(1) + '/'
|
||||
if dirent not in self.directory_entries:
|
||||
self.directory_entries[dirent] = []
|
||||
|
||||
if dirent not in dirs_seen:
|
||||
dirs_seen[dirent] = True
|
||||
self.directories.append(dirent)
|
||||
|
||||
entry = name.replace(dirent, '')
|
||||
entry_match = dir_regex.match(entry)
|
||||
if entry_match:
|
||||
subdir = entry_match.group(1) + '/'
|
||||
if subdir in subdirs_seen:
|
||||
continue
|
||||
subdirs_seen[subdir] = True
|
||||
dir_entries[dirent].append(subdir)
|
||||
else:
|
||||
dir_entries[dirent].append(entry)
|
||||
else:
|
||||
self.subtree_types.append(self.types[idx])
|
||||
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(v=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_gui_historybrowser
|
||||
|
||||
def remember_gui_settings(self):
|
||||
return self.global_cola_savewindowsettings
|
||||
|
||||
def save_at_exit(self):
|
||||
return self.global_cola_saveatexit
|
||||
|
||||
def get_tree_node(self, idx):
|
||||
return (self.get_types()[idx],
|
||||
self.get_sha1s()[idx],
|
||||
self.get_names()[idx] )
|
||||
|
||||
def get_subtree_node(self, idx):
|
||||
return (self.get_subtree_types()[idx],
|
||||
self.get_subtree_sha1s()[idx],
|
||||
self.get_subtree_names()[idx] )
|
||||
|
||||
def get_all_branches(self):
|
||||
return (self.get_local_branches() + self.get_remote_branches())
|
||||
|
||||
def set_remote(self, remote):
|
||||
if not remote: return
|
||||
self.set_param('remote', remote)
|
||||
branches = utils.grep('%s/\S+$' % remote,
|
||||
self.branch_list(remote=True),
|
||||
squash=False)
|
||||
self.set_remote_branches(branches)
|
||||
|
||||
def add_signoff(self,*rest):
|
||||
"""Adds a standard Signed-off by: tag to the end
|
||||
of the current commit message."""
|
||||
|
||||
msg = self.get_commitmsg()
|
||||
signoff =('\n\nSigned-off-by: %s <%s>\n'
|
||||
% (self.get_local_user_name(), self.get_local_user_email()))
|
||||
if signoff not in msg:
|
||||
self.set_commitmsg(msg + signoff)
|
||||
|
||||
def apply_diff(self, filename):
|
||||
return self.git.apply(filename, index=True, cached=True)
|
||||
|
||||
def apply_diff_to_worktree(self, filename):
|
||||
return self.git.apply(filename)
|
||||
|
||||
def load_commitmsg(self, path):
|
||||
file = open(path, 'r')
|
||||
contents = file.read()
|
||||
file.close()
|
||||
self.set_commitmsg(contents)
|
||||
|
||||
def get_prev_commitmsg(self,*rest):
|
||||
"""Queries git for the latest commit message and sets it in
|
||||
self.commitmsg."""
|
||||
commit_msg = []
|
||||
commit_lines = self.git.show('HEAD').split('\n')
|
||||
for idx, msg in enumerate(commit_lines):
|
||||
if idx < 4: continue
|
||||
msg = msg.lstrip()
|
||||
if msg.startswith('diff --git'):
|
||||
commit_msg.pop()
|
||||
break
|
||||
commit_msg.append(msg)
|
||||
self.set_commitmsg('\n'.join(commit_msg).rstrip())
|
||||
|
||||
def update_status(self):
|
||||
# This allows us to defer notification until the
|
||||
# we finish processing data
|
||||
notify_enabled = self.get_notify()
|
||||
self.set_notify(False)
|
||||
|
||||
# Reset the staged and unstaged model lists
|
||||
# NOTE: the model's unstaged list is used to
|
||||
# hold both modified and untracked files.
|
||||
self.staged = []
|
||||
self.modified = []
|
||||
self.untracked = []
|
||||
|
||||
# Read git status items
|
||||
(staged_items,
|
||||
modified_items,
|
||||
untracked_items) = self.parse_status()
|
||||
|
||||
# Gather items to be committed
|
||||
for staged in staged_items:
|
||||
if staged not in self.get_staged():
|
||||
self.add_staged(staged)
|
||||
|
||||
# Gather unindexed items
|
||||
for modified in modified_items:
|
||||
if modified not in self.get_modified():
|
||||
self.add_modified(modified)
|
||||
|
||||
# Gather untracked items
|
||||
for untracked in untracked_items:
|
||||
if untracked not in self.get_untracked():
|
||||
self.add_untracked(untracked)
|
||||
|
||||
self.set_currentbranch(self.current_branch())
|
||||
self.set_unstaged(self.get_modified() + self.get_untracked())
|
||||
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('')
|
||||
# Re-enable notifications and emit changes
|
||||
self.set_notify(notify_enabled)
|
||||
self.notify_observers('staged','unstaged')
|
||||
|
||||
def delete_branch(self, branch):
|
||||
return self.git.branch(branch, D=True)
|
||||
|
||||
def get_revision_sha1(self, idx):
|
||||
return self.get_revisions()[idx]
|
||||
|
||||
def apply_font_size(self, param, default):
|
||||
old_font = self.get_param(param)
|
||||
if not old_font:
|
||||
old_font = default
|
||||
|
||||
size = self.get_param(param+'_size')
|
||||
props = old_font.split(',')
|
||||
props[1] = str(size)
|
||||
new_font = ','.join(props)
|
||||
|
||||
self.set_param(param, new_font)
|
||||
|
||||
def read_font_size(self, param, new_font):
|
||||
new_size = int(new_font.split(',')[1])
|
||||
self.set_param(param, new_size)
|
||||
|
||||
def get_commit_diff(self, sha1):
|
||||
commit = self.git.show(sha1)
|
||||
first_newline = commit.index('\n')
|
||||
if commit[first_newline+1:].startswith('Merge:'):
|
||||
return (commit + '\n\n'
|
||||
+ self.diff_helper(commit=sha1,
|
||||
cached=False,
|
||||
suppress_header=False))
|
||||
else:
|
||||
return commit
|
||||
|
||||
def get_diff_and_status(self, idx, staged=True):
|
||||
if staged:
|
||||
filename = self.get_staged()[idx]
|
||||
if os.path.exists(filename):
|
||||
status = 'Staged for commit'
|
||||
else:
|
||||
status = 'Staged for removal'
|
||||
diff = self.diff_helper(filename=filename,
|
||||
cached=True)
|
||||
else:
|
||||
filename = self.get_unstaged()[idx]
|
||||
if os.path.isdir(filename):
|
||||
status = 'Untracked directory'
|
||||
diff = '\n'.join(os.listdir(filename))
|
||||
elif filename in self.get_modified():
|
||||
status = 'Modified, not staged'
|
||||
diff = self.diff_helper(filename=filename,
|
||||
cached=False)
|
||||
else:
|
||||
status = 'Untracked, not staged'
|
||||
|
||||
file_type = utils.run_cmd('file', '-b', filename)
|
||||
if 'binary' in file_type or 'data' in file_type:
|
||||
diff = utils.run_cmd('hexdump', '-C', filename)
|
||||
else:
|
||||
if os.path.exists(filename):
|
||||
file = open(filename, 'r')
|
||||
diff = file.read()
|
||||
file.close()
|
||||
else:
|
||||
diff = ''
|
||||
return diff, status
|
||||
|
||||
def stage_modified(self):
|
||||
output = self.git.add(self.get_modified())
|
||||
self.update_status()
|
||||
return output
|
||||
|
||||
def stage_untracked(self):
|
||||
output = self.git.add(self.get_untracked())
|
||||
self.update_status()
|
||||
return output
|
||||
|
||||
def reset(self, *items):
|
||||
output = self.git.reset('--', *items)
|
||||
self.update_status()
|
||||
return output
|
||||
|
||||
def unstage_all(self):
|
||||
self.git.reset('--', *self.get_staged())
|
||||
self.update_status()
|
||||
|
||||
def save_gui_settings(self):
|
||||
self.config_set('cola.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_dir(self):
|
||||
return os.environ.get('TMP', os.environ.get('TMPDIR', '/tmp'))
|
||||
|
||||
def get_tmp_file_pattern(self):
|
||||
return os.path.join(self.get_tmp_dir(), '*.git.%s.*' % os.getpid())
|
||||
|
||||
def get_tmp_filename(self, prefix=''):
|
||||
# Allow TMPDIR/TMP with a fallback to /tmp
|
||||
basename = (prefix+'.git.%s.%s'
|
||||
% (os.getpid(), time.time())).replace(os.sep, '-')
|
||||
return os.path.join(self.get_tmp_dir(), 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,
|
||||
branch=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))
|
||||
elif branch:
|
||||
argv.append(branch)
|
||||
|
||||
if filename:
|
||||
argv.append('--')
|
||||
if type(filename) is list:
|
||||
argv.extend(filename)
|
||||
else:
|
||||
argv.append(filename)
|
||||
|
||||
diffoutput = self.git.diff(R=reverse,
|
||||
color=color,
|
||||
cached=cached,
|
||||
patch_with_raw=True,
|
||||
unified=self.diff_context,
|
||||
with_raw_output=True,
|
||||
*argv)
|
||||
diff = diffoutput.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.get_git_dir() ]
|
||||
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+|#\tcopied:\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_extended_output": 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)
|
||||
(status, stdout, stderr) = self.git.fetch(v=True, *args, **kwargs)
|
||||
return (status, stdout + stderr)
|
||||
|
||||
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)
|
||||
(status, stdout, stderr) = self.git.push(*args, **kwargs)
|
||||
return (status, stdout + stderr)
|
||||
|
||||
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)
|
||||
(status, stdout, stderr) = self.git.pull(v=True, *args, **kwargs)
|
||||
return (status, stdout + stderr)
|
||||
|
||||
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 ]
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
from pprint import pformat
|
||||
|
||||
class Observer(object):
|
||||
"""
|
||||
Observers receive notify(*attributes) messages from their
|
||||
subjects whenever new data arrives. This notify() message signifies
|
||||
that an observer should update its internal state/view.
|
||||
"""
|
||||
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
self.model.add_observer(self)
|
||||
self.__debug = False
|
||||
|
||||
def __del__(self):
|
||||
self.model.remove_observer(self)
|
||||
|
||||
def set_debug(self, enabled):
|
||||
self.__debug = enabled
|
||||
|
||||
def notify(self, *attributes):
|
||||
"""Called by the model to notify Observers about changes."""
|
||||
# We can be notified about multiple attribute changes at once
|
||||
model = self.model
|
||||
for attr in attributes:
|
||||
notify = model.get_notify()
|
||||
model.set_notify(False) # NOTIFY OFF
|
||||
|
||||
value = model.get_param(attr)
|
||||
self.subject_changed(attr, value)
|
||||
|
||||
model.set_notify(notify) # NOTIFY ON
|
||||
|
||||
def subject_changed(self, attr, value):
|
||||
"""This method handles updating of the observer/UI.
|
||||
This must be implemented in each concrete observer class."""
|
||||
|
||||
msg = 'Concrete Observers must override subject_changed().'
|
||||
raise NotImplementedError, msg
|
||||
@@ -0,0 +1,207 @@
|
||||
#!/usr/bin/env python
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import QObject
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
from PyQt4.QtCore import QDate
|
||||
from PyQt4.QtGui import QDateEdit
|
||||
from PyQt4.QtGui import QSpinBox
|
||||
from PyQt4.QtGui import QPixmap
|
||||
from PyQt4.QtGui import QTextEdit
|
||||
from PyQt4.QtGui import QLineEdit
|
||||
from PyQt4.QtGui import QListWidget
|
||||
from PyQt4.QtGui import QCheckBox
|
||||
from PyQt4.QtGui import QFontComboBox
|
||||
from PyQt4.QtGui import QAbstractButton
|
||||
from PyQt4.QtGui import QSplitter
|
||||
from PyQt4.QtGui import QAction
|
||||
|
||||
from cola.observer import Observer
|
||||
|
||||
class QObserver(Observer, QObject):
|
||||
|
||||
def __init__(self, model, view, *args, **kwargs):
|
||||
Observer.__init__(self, model)
|
||||
QObject.__init__(self)
|
||||
|
||||
self.view = view
|
||||
|
||||
self.__actions = {}
|
||||
self.__callbacks = {}
|
||||
self.__model_to_view = {}
|
||||
self.__view_to_model = {}
|
||||
self.__connected = []
|
||||
|
||||
# Call the subclass's startup routine
|
||||
self.init(model, view, *args, **kwargs)
|
||||
|
||||
def init(self, model, view, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def SLOT(self, *args):
|
||||
"""Default slot to handle all Qt callbacks.
|
||||
This method delegates to callbacks from add_signals."""
|
||||
|
||||
widget = self.sender()
|
||||
sender = str(widget.objectName())
|
||||
|
||||
if sender in self.__callbacks:
|
||||
self.__callbacks[sender](*args)
|
||||
|
||||
elif sender in self.__view_to_model:
|
||||
model = self.model
|
||||
model_param = self.__view_to_model[sender]
|
||||
if isinstance(widget, QTextEdit):
|
||||
value = unicode(widget.toPlainText())
|
||||
model.set_param(model_param, value,
|
||||
notify=False)
|
||||
elif isinstance(widget, QLineEdit):
|
||||
value = unicode(widget.text())
|
||||
model.set_param(model_param, value)
|
||||
elif isinstance(widget, QCheckBox):
|
||||
model.set_param(model_param, widget.isChecked())
|
||||
elif isinstance(widget, QSpinBox):
|
||||
model.set_param(model_param, widget.value())
|
||||
elif isinstance(widget, QFontComboBox):
|
||||
value = unicode(widget.currentFont().toString())
|
||||
model.set_param(model_param, value)
|
||||
elif isinstance(widget, QDateEdit):
|
||||
fmt = Qt.ISODate
|
||||
value = str(widget.date().toString(fmt))
|
||||
model.set_param(model_param, value)
|
||||
elif isinstance(widget, QListWidget):
|
||||
pass
|
||||
else:
|
||||
print("SLOT(): Unknown widget:", sender, widget)
|
||||
|
||||
def connect(self, obj, signal_str, *args):
|
||||
"""Convenience function so that subclasses do not have
|
||||
to import QtCore.SIGNAL."""
|
||||
signal = signal_str
|
||||
if type(signal) is str:
|
||||
signal = SIGNAL(signal)
|
||||
return QObject.connect(obj, signal, *args)
|
||||
|
||||
def add_signals(self, signal_str, *objects):
|
||||
"""Connects object's signal to the QObserver."""
|
||||
for obj in objects:
|
||||
self.connect(obj, signal_str, self.SLOT)
|
||||
|
||||
def add_callbacks(self, **callbacks):
|
||||
"""Registers callbacks that are called in response to GUI events."""
|
||||
for sender, callback in callbacks.iteritems():
|
||||
self.__callbacks[sender] = callback
|
||||
self.autoconnect(getattr(self.view, sender))
|
||||
|
||||
def add_observables(self, *params):
|
||||
"""This method assumes that widgets and model params
|
||||
share the same name."""
|
||||
for param in params:
|
||||
self.model_to_view(param, getattr(self.view, param))
|
||||
|
||||
def model_to_view(self, model_param, *widgets):
|
||||
"""Binds model params to qt widgets(model->view)"""
|
||||
self.__model_to_view[model_param] = widgets
|
||||
for w in widgets:
|
||||
view = str(w.objectName())
|
||||
self.__view_to_model[view] = model_param
|
||||
self.__autoconnect(w)
|
||||
|
||||
def autoconnect(self, *widgets):
|
||||
for w in widgets:
|
||||
self.__autoconnect(w)
|
||||
|
||||
def __autoconnect(self, widget):
|
||||
"""Automagically connects Qt widgets to QObserver.SLOT"""
|
||||
|
||||
if widget in self.__connected: return
|
||||
self.__connected.append(widget)
|
||||
|
||||
if isinstance(widget, QTextEdit):
|
||||
self.add_signals('textChanged()', widget)
|
||||
elif isinstance(widget, QLineEdit):
|
||||
self.add_signals('textChanged(const QString&)', widget)
|
||||
elif isinstance(widget, QListWidget):
|
||||
self.add_signals('itemSelectionChanged()', widget)
|
||||
self.add_signals('itemClicked(QListWidgetItem *)', widget)
|
||||
self.add_signals('itemDoubleClicked(QListWidgetItem*)', widget)
|
||||
elif isinstance(widget, QAbstractButton):
|
||||
self.add_signals('released()', widget)
|
||||
elif isinstance(widget, QAction):
|
||||
self.add_signals('triggered()', widget)
|
||||
elif isinstance(widget, QCheckBox):
|
||||
self.add_signals('stateChanged(int)', widget)
|
||||
elif isinstance(widget, QSpinBox):
|
||||
self.add_signals('valueChanged(int)', widget)
|
||||
elif isinstance(widget, QFontComboBox):
|
||||
self.add_signals('currentFontChanged(const QFont&)', widget)
|
||||
elif isinstance(widget, QSplitter):
|
||||
self.add_signals('splitterMoved(int,int)', widget)
|
||||
elif isinstance(widget, QDateEdit):
|
||||
self.add_signals('dateChanged(const QDate&)', widget)
|
||||
else:
|
||||
raise Exception('Asked to connect unknown widget:\n\t%s => %s'
|
||||
% (type(widget), str(widget.objectName())))
|
||||
|
||||
def add_actions(self, **kwargs):
|
||||
"""Register view actions that are called in response to
|
||||
view changes.(view->model)"""
|
||||
for model_param, callback in kwargs.iteritems():
|
||||
if type(callback) is list:
|
||||
self.__actions[model_param] = callback
|
||||
else:
|
||||
self.__actions[model_param] = [callback]
|
||||
|
||||
def subject_changed(self, param, value):
|
||||
"""Sends a model param to the view(model->view)"""
|
||||
|
||||
if param in self.__model_to_view:
|
||||
notify = self.model.get_notify()
|
||||
self.model.set_notify(False)
|
||||
for widget in self.__model_to_view[param]:
|
||||
if isinstance(widget, QSpinBox):
|
||||
widget.setValue(value)
|
||||
elif isinstance(widget, QPixmap):
|
||||
widget.load(value)
|
||||
elif isinstance(widget, QTextEdit):
|
||||
widget.setText(value)
|
||||
elif isinstance(widget, QLineEdit):
|
||||
widget.setText(value)
|
||||
elif isinstance(widget, QListWidget):
|
||||
widget.clear()
|
||||
for i in value:
|
||||
widget.addItem(i)
|
||||
elif isinstance(widget, QCheckBox):
|
||||
widget.setChecked(value)
|
||||
elif isinstance(widget, QFontComboBox):
|
||||
font = widget.currentFont()
|
||||
font.fromString(value)
|
||||
elif isinstance(widget, QDateEdit):
|
||||
if not value: return
|
||||
fmt = Qt.ISODate
|
||||
date = QDate.fromString(value, fmt)
|
||||
if date:
|
||||
widget.setDate(date)
|
||||
else:
|
||||
print('subject_changed(): Unknown widget:',
|
||||
str(widget.objectName()), widget, value)
|
||||
self.model.set_notify(notify)
|
||||
|
||||
if param not in self.__actions:
|
||||
return
|
||||
widgets = []
|
||||
if param in self.__model_to_view:
|
||||
for widget in self.__model_to_view[param]:
|
||||
widgets.append(widget)
|
||||
# Call the model callback w/ the view's widgets as the args
|
||||
for action in self.__actions[param]:
|
||||
action(*widgets)
|
||||
|
||||
def refresh_view(self, *params):
|
||||
if not params:
|
||||
params= tuple(self.__model_to_view.keys()
|
||||
+self.__actions.keys())
|
||||
notified = []
|
||||
for param in params:
|
||||
if param not in notified:
|
||||
notified.append(param)
|
||||
self.model.notify_observers(*notified)
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
import os
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.QtGui import QClipboard
|
||||
from PyQt4.QtGui import QFileDialog
|
||||
from PyQt4.QtGui import QIcon
|
||||
from PyQt4.QtGui import QListWidgetItem
|
||||
from PyQt4.QtGui import QMessageBox
|
||||
|
||||
from cola import utils
|
||||
|
||||
LOGGER = None
|
||||
|
||||
def log(output, quiet=True, doraise=False):
|
||||
if not LOGGER: return
|
||||
LOGGER.log(output)
|
||||
if quiet: return
|
||||
LOGGER.show()
|
||||
if not doraise: return
|
||||
raise_logger()
|
||||
|
||||
def raise_logger():
|
||||
LOGGER.raise_()
|
||||
|
||||
def input(msg, title=None):
|
||||
if title is None:
|
||||
title = msg
|
||||
parent = QtGui.qApp.activeWindow()
|
||||
result = QtGui.QInputDialog.getText(parent, msg, title)
|
||||
return (unicode(result[0]), result[1])
|
||||
|
||||
def close_log_window():
|
||||
LOGGER.hide()
|
||||
LOGGER.done(0)
|
||||
|
||||
def show_output(output, **kwargs):
|
||||
if not output: return
|
||||
log(output, quiet=False)
|
||||
|
||||
def toggle_log_window():
|
||||
if not LOGGER: return
|
||||
if LOGGER.isVisible():
|
||||
LOGGER.hide()
|
||||
else:
|
||||
LOGGER.show()
|
||||
LOGGER.raise_()
|
||||
|
||||
def create_listwidget_item(text, filename):
|
||||
icon = QIcon(filename)
|
||||
item = QListWidgetItem()
|
||||
item.setIcon(icon)
|
||||
item.setText(text)
|
||||
return item
|
||||
|
||||
def information(title, message=None):
|
||||
"""Launches a QMessageBox information with the
|
||||
provided title and message."""
|
||||
if message is None:
|
||||
message = title
|
||||
QMessageBox.information(QtGui.qApp.activeWindow(), title, message)
|
||||
|
||||
def get_selected_row(list_widget):
|
||||
"""Returns a(row_number, is_selected) tuple for a QListWidget."""
|
||||
row = list_widget.currentRow()
|
||||
item = list_widget.item(row)
|
||||
selected = item is not None and item.isSelected()
|
||||
return(row, selected)
|
||||
|
||||
def get_selection_list(listwidget, items):
|
||||
"""Returns an array of model items that correspond to
|
||||
the selected QListWidget indices."""
|
||||
selected = []
|
||||
itemcount = listwidget.count()
|
||||
widgetitems = [ listwidget.item(idx) for idx in range(itemcount) ]
|
||||
|
||||
for item, widgetitem in zip(items, widgetitems):
|
||||
if widgetitem.isSelected():
|
||||
selected.append(item)
|
||||
return selected
|
||||
|
||||
def get_selected_item(list_widget, items):
|
||||
row, selected = get_selected_row(list_widget)
|
||||
if selected and row < len(items):
|
||||
return items[row]
|
||||
else:
|
||||
return None
|
||||
|
||||
def open_dialog(parent, title, filename=None):
|
||||
qstr = QFileDialog.getOpenFileName(parent, parent.tr(title), filename)
|
||||
return unicode(qstr)
|
||||
|
||||
def opendir_dialog(parent, title, directory):
|
||||
flags = QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontResolveSymlinks
|
||||
qstr = QFileDialog.getExistingDirectory(parent, directory,
|
||||
parent.tr(title),
|
||||
flags)
|
||||
return unicode(qstr)
|
||||
|
||||
def save_dialog(parent, title, filename=None):
|
||||
qstr = QFileDialog.getSaveFileName(parent, parent.tr(title), filename)
|
||||
return unicode(qstr)
|
||||
|
||||
def dir_dialog(parent, title, directory):
|
||||
directory = QFileDialog.getExistingDirectory(parent, title, directory)
|
||||
return unicode(directory)
|
||||
|
||||
def get_qicon(filename):
|
||||
icon = utils.get_icon(filename)
|
||||
return QIcon(icon)
|
||||
|
||||
def question(parent, title, message, default=True):
|
||||
"""Launches a QMessageBox question with the provided title and message.
|
||||
Passing "default=False" will make "No" the default choice."""
|
||||
yes = QMessageBox.Yes
|
||||
no = QMessageBox.No
|
||||
buttons = yes | no
|
||||
if default:
|
||||
default = yes
|
||||
else:
|
||||
default = no
|
||||
result = QMessageBox.question(parent, title, message, buttons, default)
|
||||
return result == QMessageBox.Yes
|
||||
|
||||
def set_clipboard(text):
|
||||
QtGui.qApp.clipboard().setText(text, QClipboard.Clipboard)
|
||||
QtGui.qApp.clipboard().setText(text, QClipboard.Selection)
|
||||
|
||||
def add_items(widget, items):
|
||||
for item in items: widget.addItem(item)
|
||||
|
||||
def set_items(widget, items):
|
||||
widget.clear()
|
||||
add_items(widget, items)
|
||||
|
||||
def tr(txt):
|
||||
trtext = unicode(QtGui.qApp.tr(txt))
|
||||
if trtext.endswith('@@verb'):
|
||||
trtext = trtext.replace('@@verb','')
|
||||
if trtext.endswith('@@noun'):
|
||||
trtext = trtext.replace('@@noun','')
|
||||
return trtext
|
||||
|
||||
def create_item(filename, staged, untracked=False):
|
||||
"""Given a filename, return a QListWidgetItem suitable
|
||||
for adding to a QListWidget. "staged" and "untracked"
|
||||
controls whether to use the appropriate icons."""
|
||||
if staged:
|
||||
if os.path.exists(filename):
|
||||
icon_file = utils.get_icon('staged.png')
|
||||
else:
|
||||
icon_file = utils.get_icon('removed.png')
|
||||
elif untracked:
|
||||
icon_file = utils.get_icon('untracked.png')
|
||||
else:
|
||||
icon_file = utils.get_file_icon(filename)
|
||||
return create_listwidget_item(filename, icon_file)
|
||||
|
||||
def create_txt_item(txt):
|
||||
item = QListWidgetItem()
|
||||
item.setText(txt)
|
||||
return item
|
||||
|
||||
def update_listwidget(widget, items, staged=True,
|
||||
untracked=False, append=False):
|
||||
"""Populate a QListWidget with custom icon items."""
|
||||
if not append: widget.clear()
|
||||
add_items(widget, [ create_item(i, staged, untracked) for i in items ])
|
||||
|
||||
def set_listwidget_strings(widget, items):
|
||||
widget.clear()
|
||||
add_items(widget, [ create_txt_item(i) for i in items ])
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python
|
||||
HAS_SIMPLEJSON = False
|
||||
try:
|
||||
import simplejson
|
||||
HAS_SIMPLEJSON = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import os
|
||||
import user
|
||||
from cola.model import Model
|
||||
|
||||
class SettingsModel(Model):
|
||||
|
||||
def init(self):
|
||||
self.create( bookmarks = [] )
|
||||
if not HAS_SIMPLEJSON:
|
||||
return
|
||||
settings = self.path()
|
||||
if os.path.exists(settings):
|
||||
self.load(settings)
|
||||
|
||||
def path(self):
|
||||
return os.path.join(user.home, '.cola')
|
||||
|
||||
def save_all_settings(self):
|
||||
if not HAS_SIMPLEJSON:
|
||||
return
|
||||
self.save(self.path())
|
||||
Executable
+169
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/python
|
||||
import re
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtGui import QFont
|
||||
from PyQt4.QtGui import QSyntaxHighlighter
|
||||
from PyQt4.QtGui import QTextCharFormat
|
||||
from PyQt4.QtGui import QColor
|
||||
|
||||
class GenericSyntaxHighligher(QSyntaxHighlighter):
|
||||
|
||||
def __init__(self, doc):
|
||||
QSyntaxHighlighter.__init__(self, doc)
|
||||
self.__rules = []
|
||||
|
||||
FINAL_STR = '__FINAL__:'
|
||||
def final(self, pattern=''):
|
||||
"""
|
||||
Denotes that a pattern is the final pattern that should
|
||||
be matched. If this pattern matches no other formats
|
||||
will be applied, even if they would have matched.
|
||||
"""
|
||||
return GenericSyntaxHighligher.FINAL_STR + pattern
|
||||
|
||||
def create_rules(self, *rules):
|
||||
if len(rules) % 2:
|
||||
raise Exception('create_rules requires an even '
|
||||
'number of arguments.')
|
||||
for idx, rule in enumerate(rules):
|
||||
if idx % 2:
|
||||
continue
|
||||
formats = rules[idx+1]
|
||||
terminal = rule.startswith(self.final())
|
||||
if terminal:
|
||||
rule = rule[len(self.final()):]
|
||||
regex = re.compile(rule)
|
||||
self.__rules.append((regex, formats, terminal,))
|
||||
|
||||
def get_formats(self, line):
|
||||
matched = []
|
||||
for regex, fmts, terminal in self.__rules:
|
||||
match = regex.match(line)
|
||||
if match:
|
||||
matched.append([match, fmts])
|
||||
if terminal: return matched
|
||||
return matched
|
||||
|
||||
def mkformat(self, fg=None, bg=None, bold=False):
|
||||
format = QTextCharFormat()
|
||||
if fg: format.setForeground(fg)
|
||||
if bg: format.setBackground(bg)
|
||||
if bold: format.setFontWeight(QFont.Bold)
|
||||
return format
|
||||
|
||||
def highlightBlock(self, qstr):
|
||||
ascii = qstr.toAscii().data()
|
||||
if not ascii: return
|
||||
formats = self.get_formats(ascii)
|
||||
if not formats: return
|
||||
for match, fmts in formats:
|
||||
start = match.start()
|
||||
end = match.end()
|
||||
groups = match.groups()
|
||||
|
||||
# No groups in the regex, assume this is a single rule
|
||||
# that spans the entire line
|
||||
if not groups:
|
||||
self.setFormat(0, len(ascii), fmts)
|
||||
continue
|
||||
|
||||
# Groups exist, rule is a tuple corresponding to group
|
||||
for grpidx, group in enumerate(groups):
|
||||
# allow empty matches
|
||||
if not group: continue
|
||||
# allow None as a no-op format
|
||||
length = len(group)
|
||||
if fmts[grpidx]:
|
||||
self.setFormat(start, start+length,
|
||||
fmts[grpidx])
|
||||
start += length
|
||||
|
||||
class DiffSyntaxHighlighter(GenericSyntaxHighligher):
|
||||
def __init__(self, doc,whitespace=True):
|
||||
GenericSyntaxHighligher.__init__(self,doc)
|
||||
|
||||
diffstat = self.mkformat(Qt.blue, bold=True)
|
||||
diffstat_add = self.mkformat(Qt.darkGreen, bold=True)
|
||||
diffstat_remove = self.mkformat(Qt.red, bold=True)
|
||||
|
||||
bg_green = QColor(Qt.green)
|
||||
bg_green.setAlpha(128)
|
||||
|
||||
bg_red = QColor(Qt.red)
|
||||
bg_red.setAlpha(128)
|
||||
|
||||
diff_begin = self.mkformat(Qt.darkCyan, bold=True)
|
||||
diff_head = self.mkformat(Qt.darkYellow)
|
||||
diff_add = self.mkformat(bg=bg_green)
|
||||
diff_remove = self.mkformat(bg=bg_red)
|
||||
|
||||
if whitespace:
|
||||
bad_ws = self.mkformat(Qt.black, Qt.red)
|
||||
|
||||
# We specify the whitespace rule last so that it is
|
||||
# applied after the diff addition/removal rules.
|
||||
# The rules for the header
|
||||
diff_bgn_rgx = self.final('^@@|^\+\+\+|^---')
|
||||
diff_hd1_rgx = self.final('^diff --git')
|
||||
diff_hd2_rgx = self.final('^index \S+\.\.\S+')
|
||||
diff_hd3_rgx = self.final('^new file mode')
|
||||
diff_add_rgx = self.final('^\+')
|
||||
diff_rmv_rgx = self.final('^-')
|
||||
diff_sts_rgx = ('(.+\|.+?)(\d+)(.+?)([\+]*?)([-]*?)$')
|
||||
diff_sum_rgx = ('(\s+\d+ files changed[^\d]*)'
|
||||
'(:?\d+ insertions[^\d]*)'
|
||||
'(:?\d+ deletions.*)$')
|
||||
|
||||
self.create_rules(diff_bgn_rgx, diff_begin,
|
||||
diff_hd1_rgx, diff_head,
|
||||
diff_hd2_rgx, diff_head,
|
||||
diff_hd3_rgx, diff_head,
|
||||
diff_add_rgx, diff_add,
|
||||
diff_rmv_rgx, diff_remove,
|
||||
diff_sts_rgx, (None, diffstat,
|
||||
None, diffstat_add,
|
||||
diffstat_remove),
|
||||
diff_sum_rgx, (diffstat,
|
||||
diffstat_add,
|
||||
diffstat_remove))
|
||||
if whitespace:
|
||||
self.create_rules('(..*?)(\s+)$', (None, bad_ws))
|
||||
|
||||
class LogSyntaxHighlighter(GenericSyntaxHighligher):
|
||||
def __init__(self, doc):
|
||||
GenericSyntaxHighligher.__init__(self,doc)
|
||||
|
||||
blue = self.mkformat(Qt.blue, bold=True)
|
||||
black = self.mkformat(Qt.black, bold=True)
|
||||
dark_cyan = self.mkformat(Qt.darkCyan, bold=True)
|
||||
|
||||
blue_black_rgx = '^([^:]+:)(.*)$'
|
||||
dark_cyan_rgx = self.final('^\w{3}\W+\w{3}\W+\d+\W+'
|
||||
'[:0-9]+\W+\d{4}$')
|
||||
|
||||
self.create_rules(dark_cyan_rgx, dark_cyan,
|
||||
blue_black_rgx, (blue, black))
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
from PyQt4 import QtCore, QtGui
|
||||
class SyntaxTestDialog(QtGui.QDialog):
|
||||
def __init__(self, parent):
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
def setupUi(self, dialog):
|
||||
dialog.resize(QtCore.QSize(QtCore.QRect(0,0,720,512).size()).expandedTo(dialog.minimumSizeHint()))
|
||||
self.vboxlayout = QtGui.QVBoxLayout(dialog)
|
||||
self.vboxlayout.setObjectName('vboxlayout')
|
||||
self.output_text = QtGui.QTextEdit(dialog)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily('Monospace')
|
||||
font.setPointSize(13)
|
||||
self.output_text.setFont(font)
|
||||
self.output_text.setAcceptDrops(False)
|
||||
self.vboxlayout.addWidget(self.output_text)
|
||||
DiffSyntaxHighlighter(self.output_text.document())
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
dialog = SyntaxTestDialog(app.activeWindow())
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
+440
@@ -0,0 +1,440 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from cStringIO import StringIO
|
||||
|
||||
import defaults
|
||||
|
||||
PREFIX = os.path.realpath(os.path.dirname(os.path.dirname(sys.argv[0])))
|
||||
QMDIR = os.path.join(PREFIX, 'share', 'cola', 'qm')
|
||||
ICONSDIR = os.path.join(PREFIX, 'share', 'cola', 'icons')
|
||||
KNOWN_FILE_TYPES = {
|
||||
'ascii c': 'c.png',
|
||||
'python': 'script.png',
|
||||
'ruby': 'script.png',
|
||||
'shell': 'script.png',
|
||||
'perl': 'script.png',
|
||||
'java': 'script.png',
|
||||
'assembler': 'binary.png',
|
||||
'binary': 'binary.png',
|
||||
'byte': 'binary.png',
|
||||
'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)
|
||||
if match:
|
||||
locale = match.group(1)
|
||||
|
||||
basename = locale.split('_')[0]
|
||||
|
||||
return os.path.join(QMDIR, basename +'.qm')
|
||||
|
||||
def ident_file_type(filename):
|
||||
"""Returns an icon based on the contents of filename."""
|
||||
if os.path.exists(filename):
|
||||
fileinfo = run_cmd('file','-b',filename)
|
||||
for filetype, iconname in KNOWN_FILE_TYPES.iteritems():
|
||||
if filetype in fileinfo.lower():
|
||||
return iconname
|
||||
else:
|
||||
return 'removed.png'
|
||||
# Fallback for modified files of an unknown type
|
||||
return 'generic.png'
|
||||
|
||||
def get_file_icon(filename):
|
||||
"""
|
||||
Returns the full path to an icon file corresponding to
|
||||
filename"s contents.
|
||||
"""
|
||||
icon_file = ident_file_type(filename)
|
||||
return get_icon(icon_file)
|
||||
|
||||
def get_icon(icon_file):
|
||||
return os.path.join(ICONSDIR, icon_file)
|
||||
|
||||
def fork(*args):
|
||||
if os.name in ('nt', 'dos'):
|
||||
for path in os.pathsep.split(os.environ["PATH"]):
|
||||
file = os.path.join(path, args[0]) + ".exe"
|
||||
try:
|
||||
return os.spawnv(os.P_NOWAIT, file, (file,) + args[1:])
|
||||
except os.error:
|
||||
pass
|
||||
raise IOError('cannot find executable: %s' % program)
|
||||
else:
|
||||
argv = map(shell_quote, args)
|
||||
return os.system(' '.join(argv) + '&')
|
||||
|
||||
# c = a - b
|
||||
def sublist(a,b):
|
||||
c = []
|
||||
for item in a:
|
||||
if item not in b:
|
||||
c.append(item)
|
||||
return c
|
||||
|
||||
__grep_cache = {}
|
||||
def grep(pattern, items, squash=True):
|
||||
isdict = type(items) is dict
|
||||
if pattern in __grep_cache:
|
||||
regex = __grep_cache[pattern]
|
||||
else:
|
||||
regex = __grep_cache[pattern] = re.compile(pattern)
|
||||
matched = []
|
||||
matchdict = {}
|
||||
for item in items:
|
||||
match = regex.match(item)
|
||||
if not match: continue
|
||||
groups = match.groups()
|
||||
if not groups:
|
||||
subitems = match.group(0)
|
||||
else:
|
||||
if len(groups) == 1:
|
||||
subitems = groups[0]
|
||||
else:
|
||||
subitems = list(groups)
|
||||
if isdict:
|
||||
matchdict[item] = items[item]
|
||||
else:
|
||||
matched.append(subitems)
|
||||
|
||||
if isdict:
|
||||
return matchdict
|
||||
else:
|
||||
if squash and len(matched) == 1:
|
||||
return matched[0]
|
||||
else:
|
||||
return matched
|
||||
|
||||
def basename(path):
|
||||
"""Avoid os.path.basename because we are explicitly
|
||||
parsing git"s output, which contains /"s regardless
|
||||
of platform (a.t.m.)
|
||||
"""
|
||||
base_regex = re.compile('(.*?/)?([^/]+)$')
|
||||
match = base_regex.match(path)
|
||||
if match:
|
||||
return match.group(2)
|
||||
else:
|
||||
return pathstr
|
||||
|
||||
def shell_quote(*inputs):
|
||||
"""
|
||||
Quote strings so that they can be suitably martialled
|
||||
off to the shell. This method supports POSIX sh syntax.
|
||||
This is crucial to properly handle command line arguments
|
||||
with spaces, quotes, double-quotes, etc.
|
||||
"""
|
||||
|
||||
regex = re.compile('[^\w!%+,\-./:@^]')
|
||||
quote_regex = re.compile("((?:'\\''){2,})")
|
||||
|
||||
ret = []
|
||||
for input in inputs:
|
||||
if not input:
|
||||
continue
|
||||
|
||||
if '\x00' in input:
|
||||
raise AssertionError,('No way to quote strings '
|
||||
'containing null(\\000) bytes')
|
||||
|
||||
# = does need quoting else in command position it's a
|
||||
# program-local environment setting
|
||||
match = regex.search(input)
|
||||
if match and '=' not in input:
|
||||
# ' -> '\''
|
||||
input = input.replace("'", "'\\''")
|
||||
|
||||
# make multiple ' in a row look simpler
|
||||
# '\'''\'''\'' -> '"'''"'
|
||||
quote_match = quote_regex.match(input)
|
||||
if quote_match:
|
||||
quotes = match.group(1)
|
||||
input.replace(quotes, ("'" *(len(quotes)/4)) + "\"'")
|
||||
|
||||
input = "'%s'" % input
|
||||
if input.startswith("''"):
|
||||
input = input[2:]
|
||||
|
||||
if input.endswith("''"):
|
||||
input = input[:-2]
|
||||
ret.append(input)
|
||||
return ' '.join(ret)
|
||||
|
||||
HEADER_LENGTH = 80
|
||||
def header(msg):
|
||||
pad = HEADER_LENGTH - len(msg) - 4 # len(':+') + len('+:')
|
||||
extra = pad % 2
|
||||
pad /= 2
|
||||
return(':+'
|
||||
+(' ' * pad)
|
||||
+ msg
|
||||
+(' ' * (pad + extra))
|
||||
+ '+:'
|
||||
+ '\n')
|
||||
|
||||
def parse_geom(geomstr):
|
||||
regex = re.compile('^(\d+)x(\d+)\+(\d+),(\d+)')
|
||||
match = regex.match(geomstr)
|
||||
if match:
|
||||
defaults.WIDTH = int(match.group(1))
|
||||
defaults.HEIGHT = int(match.group(2))
|
||||
defaults.X = int(match.group(3))
|
||||
defaults.Y = int(match.group(4))
|
||||
|
||||
return (defaults.WIDTH,
|
||||
defaults.HEIGHT,
|
||||
defaults.X,
|
||||
defaults.Y)
|
||||
|
||||
def get_geom():
|
||||
return '%dx%d+%d,%d' % (defaults.WIDTH,
|
||||
defaults.HEIGHT,
|
||||
defaults.X,
|
||||
defaults.Y)
|
||||
|
||||
def project_name():
|
||||
return os.path.basename(defaults.DIRECTORY)
|
||||
|
||||
def slurp(path):
|
||||
file = open(path)
|
||||
slushy = file.read()
|
||||
file.close()
|
||||
return slushy
|
||||
|
||||
def write(path, contents):
|
||||
file = open(path, 'w')
|
||||
file.write(contents)
|
||||
file.close()
|
||||
|
||||
class DiffParser(object):
|
||||
def __init__(self, model, filename='', cached=True, branch=None):
|
||||
|
||||
self.__header_re = re.compile('^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*')
|
||||
self.__headers = []
|
||||
|
||||
self.__idx = -1
|
||||
self.__diffs = []
|
||||
self.__diff_spans = []
|
||||
self.__diff_offsets = []
|
||||
|
||||
self.start = None
|
||||
self.end = None
|
||||
self.offset = None
|
||||
self.diffs = []
|
||||
self.selected = []
|
||||
|
||||
(header, diff) = model.diff_helper(filename=filename,
|
||||
branch=branch,
|
||||
with_diff_header=True,
|
||||
cached=cached and not bool(branch),
|
||||
reverse=cached or bool(branch))
|
||||
self.model = model
|
||||
self.diff = diff
|
||||
self.header = header
|
||||
self.parse_diff(diff)
|
||||
|
||||
# Always index into the non-reversed diff
|
||||
self.fwd_header, self.fwd_diff = \
|
||||
model.diff_helper(filename=filename,
|
||||
branch=branch,
|
||||
with_diff_header=True,
|
||||
cached=cached and not bool(branch),
|
||||
reverse=bool(branch))
|
||||
|
||||
def write_diff(self,filename,which,selected=False,noop=False):
|
||||
if not noop and which < len(self.diffs):
|
||||
diff = self.diffs[which]
|
||||
write(filename, self.header + '\n' + diff + '\n')
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_diffs(self):
|
||||
return self.__diffs
|
||||
|
||||
def get_diff_subset(self, diff, start, end):
|
||||
adds = 0
|
||||
deletes = 0
|
||||
newdiff = []
|
||||
local_offset = 0
|
||||
offset = self.__diff_spans[diff][0]
|
||||
diffguts = '\n'.join(self.__diffs[diff])
|
||||
|
||||
for line in self.__diffs[diff]:
|
||||
line_start = offset + local_offset
|
||||
local_offset += len(line) + 1 #\n
|
||||
line_end = offset + local_offset
|
||||
# |line1 |line2 |line3 |
|
||||
# |--selection--|
|
||||
# '-start '-end
|
||||
# selection has head of diff (line3)
|
||||
if start < line_start and end > line_start and end < line_end:
|
||||
newdiff.append(line)
|
||||
if line.startswith('+'):
|
||||
adds += 1
|
||||
if line.startswith('-'):
|
||||
deletes += 1
|
||||
# selection has all of diff (line2)
|
||||
elif start <= line_start and end >= line_end:
|
||||
newdiff.append(line)
|
||||
if line.startswith('+'):
|
||||
adds += 1
|
||||
if line.startswith('-'):
|
||||
deletes += 1
|
||||
# selection has tail of diff (line1)
|
||||
elif start >= line_start and start < line_end - 1:
|
||||
newdiff.append(line)
|
||||
if line.startswith('+'):
|
||||
adds += 1
|
||||
if line.startswith('-'):
|
||||
deletes += 1
|
||||
else:
|
||||
# Don't add new lines unless selected
|
||||
if line.startswith('+'):
|
||||
continue
|
||||
elif line.startswith('-'):
|
||||
# Don't remove lines unless selected
|
||||
newdiff.append(' ' + line[1:])
|
||||
else:
|
||||
newdiff.append(line)
|
||||
|
||||
new_count = self.__headers[diff][1] + adds - deletes
|
||||
if new_count != self.__headers[diff][3]:
|
||||
header = '@@ -%d,%d +%d,%d @@' % (
|
||||
self.__headers[diff][0],
|
||||
self.__headers[diff][1],
|
||||
self.__headers[diff][2],
|
||||
new_count)
|
||||
newdiff[0] = header
|
||||
|
||||
return (self.header + '\n' + '\n'.join(newdiff) + '\n')
|
||||
|
||||
def get_spans(self):
|
||||
return self.__diff_spans
|
||||
|
||||
def get_offsets(self):
|
||||
return self.__diff_offsets
|
||||
|
||||
def set_diff_to_offset(self, offset):
|
||||
self.offset = offset
|
||||
self.diffs, self.selected = self.get_diff_for_offset(offset)
|
||||
|
||||
def set_diffs_to_range(self, start, end):
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.diffs, self.selected = self.get_diffs_for_range(start,end)
|
||||
|
||||
def get_diff_for_offset(self, offset):
|
||||
for idx, diff_offset in enumerate(self.__diff_offsets):
|
||||
if offset < diff_offset:
|
||||
return (['\n'.join(self.__diffs[idx])], [idx])
|
||||
return ([],[])
|
||||
|
||||
def get_diffs_for_range(self, start, end):
|
||||
diffs = []
|
||||
indices = []
|
||||
for idx, span in enumerate(self.__diff_spans):
|
||||
has_end_of_diff = start >= span[0] and start < span[1]
|
||||
has_all_of_diff = start <= span[0] and end >= span[1]
|
||||
has_head_of_diff = end >= span[0] and end <= span[1]
|
||||
|
||||
selected_diff =(has_end_of_diff
|
||||
or has_all_of_diff
|
||||
or has_head_of_diff)
|
||||
if selected_diff:
|
||||
diff = '\n'.join(self.__diffs[idx])
|
||||
diffs.append(diff)
|
||||
indices.append(idx)
|
||||
return diffs, indices
|
||||
|
||||
def parse_diff(self, diff):
|
||||
total_offset = 0
|
||||
self.__idx = -1
|
||||
self.__headers = []
|
||||
|
||||
for idx, line in enumerate(diff.splitlines()):
|
||||
match = self.__header_re.match(line)
|
||||
if match:
|
||||
self.__headers.append([
|
||||
int(match.group(1)),
|
||||
int(match.group(2)),
|
||||
int(match.group(3)),
|
||||
int(match.group(4))
|
||||
])
|
||||
self.__diffs.append( [line] )
|
||||
|
||||
line_len = len(line) + 1 #\n
|
||||
self.__diff_spans.append([total_offset,
|
||||
total_offset + line_len])
|
||||
total_offset += line_len
|
||||
self.__diff_offsets.append(total_offset)
|
||||
self.__idx += 1
|
||||
else:
|
||||
if self.__idx < 0:
|
||||
errmsg = 'Malformed diff?\n\n%s' % diff
|
||||
raise AssertionError, errmsg
|
||||
line_len = len(line) + 1
|
||||
total_offset += line_len
|
||||
|
||||
self.__diffs[self.__idx].append(line)
|
||||
self.__diff_spans[-1][-1] += line_len
|
||||
self.__diff_offsets[self.__idx] += line_len
|
||||
|
||||
def process_diff_selection(self, selected, offset, selection, branch=False):
|
||||
if selection:
|
||||
start = self.fwd_diff.index(selection)
|
||||
end = start + len(selection)
|
||||
self.set_diffs_to_range(start, end)
|
||||
else:
|
||||
self.set_diff_to_offset(offset)
|
||||
selected = False
|
||||
# Process diff selection only
|
||||
if selected:
|
||||
for idx in self.selected:
|
||||
contents = self.get_diff_subset(idx, start, end)
|
||||
if contents:
|
||||
tmpfile = self.model.get_tmp_filename()
|
||||
write(tmpfile, contents)
|
||||
if branch:
|
||||
self.model.apply_diff_to_worktree(tmpfile)
|
||||
else:
|
||||
self.model.apply_diff(tmpfile)
|
||||
os.unlink(tmpfile)
|
||||
# Process a complete hunk
|
||||
else:
|
||||
for idx, diff in enumerate(self.diffs):
|
||||
tmpfile = self.model.get_tmp_filename()
|
||||
if self.write_diff(tmpfile,idx):
|
||||
if branch:
|
||||
self.model.apply_diff_to_worktree(tmpfile)
|
||||
else:
|
||||
self.model.apply_diff(tmpfile)
|
||||
os.unlink(tmpfile)
|
||||
|
||||
def sanitize_input(input):
|
||||
for c in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
|
||||
input = input.replace(c, '_')
|
||||
return input
|
||||
@@ -0,0 +1,206 @@
|
||||
import os
|
||||
import time
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtGui import qApp
|
||||
from PyQt4.QtGui import QDialog
|
||||
from PyQt4.QtGui import QMainWindow
|
||||
from PyQt4.QtGui import QCheckBox
|
||||
from PyQt4.QtGui import QSplitter
|
||||
|
||||
from cola import qtutils
|
||||
from cola.syntax import DiffSyntaxHighlighter
|
||||
from cola.syntax import LogSyntaxHighlighter
|
||||
|
||||
from main import Ui_main
|
||||
from combo import Ui_combo
|
||||
from items import Ui_items
|
||||
from remote import Ui_remote
|
||||
from commit import Ui_commit
|
||||
from logger import Ui_logger
|
||||
from search import Ui_search
|
||||
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
|
||||
|
||||
def CreateStandardView(uiclass, qtclass, *classes):
|
||||
"""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)
|
||||
for cls in classes:
|
||||
cls.init(self, parent, *args, **kwargs)
|
||||
def init(self, parent, *args, **kwargs):
|
||||
pass
|
||||
return StandardView
|
||||
|
||||
class View(CreateStandardView(Ui_main, QMainWindow)):
|
||||
"""The main cola interface."""
|
||||
def init(self, parent=None):
|
||||
self.staged.setAlternatingRowColors(True)
|
||||
self.unstaged.setAlternatingRowColors(True)
|
||||
self.set_display = self.display_text.setText
|
||||
self.set_info = self.displayLabel.setText
|
||||
self.action_undo = self.commitmsg.undo
|
||||
self.action_redo = self.commitmsg.redo
|
||||
self.action_paste = self.commitmsg.paste
|
||||
self.action_select_all = self.commitmsg.selectAll
|
||||
|
||||
# Handle automatically setting the horizontal/vertical orientation
|
||||
self.splitter.resizeEvent = self.splitter_resize_event
|
||||
|
||||
# Qt does not support noun/verbs
|
||||
self.commit_button.setText(qtutils.tr('Commit@@verb'))
|
||||
self.commit_menu.setTitle(qtutils.tr('Commit@@verb'))
|
||||
# Default to creating a new commit(i.e. not an amend commit)
|
||||
self.new_commit_radio.setChecked(True)
|
||||
self.toolbar_show_log =\
|
||||
self.toolbar.addAction(qtutils.get_qicon('git.png'),
|
||||
'Show/Hide Log Window')
|
||||
self.toolbar_show_log.setEnabled(True)
|
||||
|
||||
# Setup the default dock layout
|
||||
self.tabifyDockWidget(self.diff_dock, self.editor_dock)
|
||||
|
||||
dock_area = QtCore.Qt.TopDockWidgetArea
|
||||
self.addDockWidget(dock_area, self.status_dock)
|
||||
|
||||
toolbar_area = QtCore.Qt.BottomToolBarArea
|
||||
self.addToolBar(toolbar_area, self.toolbar)
|
||||
|
||||
# Diff/patch syntax highlighter
|
||||
DiffSyntaxHighlighter(self.display_text.document())
|
||||
|
||||
def splitter_resize_event(self, event):
|
||||
width = self.splitter.width()
|
||||
height = self.splitter.height()
|
||||
if width > height:
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
else:
|
||||
self.splitter.setOrientation(QtCore.Qt.Vertical)
|
||||
QSplitter.resizeEvent(self.splitter, event)
|
||||
|
||||
def action_cut(self):
|
||||
self.action_copy()
|
||||
self.action_delete()
|
||||
def action_copy(self):
|
||||
cursor = self.commitmsg.textCursor()
|
||||
selection = cursor.selection().toPlainText()
|
||||
qtutils.set_clipboard(selection)
|
||||
def action_delete(self):
|
||||
self.commitmsg.textCursor().removeSelectedText()
|
||||
def reset_display(self):
|
||||
self.set_display('')
|
||||
self.set_info('')
|
||||
def copy_display(self):
|
||||
cursor = self.display_text.textCursor()
|
||||
selection = cursor.selection().toPlainText()
|
||||
qtutils.set_clipboard(selection)
|
||||
def diff_selection(self):
|
||||
cursor = self.display_text.textCursor()
|
||||
offset = cursor.position()
|
||||
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(CreateStandardView(Ui_logger, QDialog)):
|
||||
"""A simple dialog to display command logs."""
|
||||
def init(self, parent=None, output=None):
|
||||
self.setWindowTitle(self.tr('Git Command Log'))
|
||||
LogSyntaxHighlighter(self.output_text.document())
|
||||
if output:
|
||||
self.set_output(output)
|
||||
def clear(self):
|
||||
self.output_text.clear()
|
||||
def set_output(self, output):
|
||||
self.output_text.setText(output)
|
||||
def log(self, output):
|
||||
if not output: return
|
||||
cursor = self.output_text.textCursor()
|
||||
cursor.movePosition(cursor.End)
|
||||
text = self.output_text
|
||||
cursor.insertText(time.asctime() + '\n')
|
||||
for line in unicode(output).splitlines():
|
||||
cursor.insertText(line + '\n')
|
||||
cursor.insertText('\n')
|
||||
cursor.movePosition(cursor.End)
|
||||
text.setTextCursor(cursor)
|
||||
|
||||
class ItemView(object):
|
||||
def init(self, parent, title="", items=[]):
|
||||
self.setWindowTitle(title)
|
||||
self.items = []
|
||||
self.items.extend(items)
|
||||
self.items_widget.addItems(items)
|
||||
def idx(self):
|
||||
return 0
|
||||
def get_selected(self):
|
||||
geom = qApp.desktop().screenGeometry()
|
||||
width = geom.width()
|
||||
height = geom.height()
|
||||
x = self.parent_view.x() + self.parent_view.width()/2 - self.width()/2
|
||||
y = self.parent_view.y() + self.parent_view.height()/3 - self.height()/2
|
||||
self.move(x, y)
|
||||
self.show()
|
||||
if self.exec_() == QDialog.Accepted:
|
||||
return self.items[self.idx()]
|
||||
else:
|
||||
return None
|
||||
|
||||
class ComboView(CreateStandardView(Ui_combo, QDialog, ItemView), ItemView):
|
||||
"""A dialog for choosing branches."""
|
||||
def idx(self):
|
||||
return self.items_widget.currentIndex()
|
||||
|
||||
class ListView(CreateStandardView(Ui_items, QDialog, ItemView), ItemView):
|
||||
"""A dialog for an item from a list."""
|
||||
def idx(self):
|
||||
return self.items_widget.currentRow()
|
||||
|
||||
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 SearchView(CreateStandardView(Ui_search, QDialog)):
|
||||
def init(self, parent=None):
|
||||
self.input.setFocus()
|
||||
DiffSyntaxHighlighter(self.commit_text.document(),
|
||||
whitespace=False)
|
||||
|
||||
class MergeView(CreateStandardView(Ui_merge, QDialog)):
|
||||
def init(self, parent=None):
|
||||
self.revision.setFocus()
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
def build(bld):
|
||||
# Compile the cola support modules
|
||||
pyqt = bld.create_obj('py')
|
||||
pyqt.inst_var = 'COLA_VIEWS'
|
||||
pyqt.find_sources_in_dirs('.')
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
import Common
|
||||
def build(bld):
|
||||
# Compile the controllers
|
||||
bld.add_subdirs('views controllers')
|
||||
# Compile the cola support modules
|
||||
pyqt = bld.create_obj('py')
|
||||
pyqt.inst_var = 'COLA_MODULES'
|
||||
pyqt.find_sources_in_dirs('.')
|
||||
|
||||
# Install the git-cola launcher
|
||||
Common.install_files('COLA_BINDIR', '', 'git-cola', chmod=0755)
|
||||
Common.symlink_as('COLA_BINDIR', 'git-cola', 'cola')
|
||||
@@ -0,0 +1,146 @@
|
||||
#! /bin/sh
|
||||
|
||||
# waf configure wrapper
|
||||
|
||||
# Fancy colors used to beautify the output a bit.
|
||||
#
|
||||
if [ $NOCOLOR ] ; then
|
||||
NORMAL=""
|
||||
BOLD=""
|
||||
RED=""
|
||||
YELLOW=""
|
||||
GREEN=""
|
||||
else
|
||||
NORMAL="\033[0m"
|
||||
BOLD="\033[1m"
|
||||
RED="\033[91m"
|
||||
YELLOW="\033[01;93m"
|
||||
GREEN="\033[92m"
|
||||
fi
|
||||
|
||||
EXIT_SUCCESS=0
|
||||
EXIT_FAILURE=1
|
||||
EXIT_ERROR=2
|
||||
EXIT_BUG=10
|
||||
|
||||
CUR_DIR=$PWD
|
||||
|
||||
#possible relative path
|
||||
WORKINGDIR=$(dirname $0)
|
||||
cd $WORKINGDIR
|
||||
#abs path
|
||||
WORKINGDIR=$(pwd)
|
||||
TESTDIR="$WORKINGDIR/t"
|
||||
cd $CUR_DIR
|
||||
|
||||
# We store waf under build/
|
||||
PATH=$WORKINGDIR/build:$PATH
|
||||
export PATH
|
||||
|
||||
# Checks for Python interpreter. Honours $PYTHON if set. Stores path to
|
||||
# interpreter in $PYTHON.
|
||||
#
|
||||
checkPython()
|
||||
{
|
||||
if [ -z "$PYTHON" ] ; then
|
||||
PYTHON=$(which python 2>/dev/null)
|
||||
fi
|
||||
printf "Checking for Python\t\t\t: "
|
||||
if [ ! -x "$PYTHON" ] ; then
|
||||
printf $RED"not found!"$NORMAL"\n"
|
||||
echo "Please make sure that the Python interpreter is available in your PATH"
|
||||
echo "or invoke configure using the PYTHON flag, e.g."
|
||||
echo "$ PYTHON=/usr/local/bin/python configure"
|
||||
exit $EXIT_FAILURE
|
||||
fi
|
||||
printf $GREEN"$PYTHON"$NORMAL"\n"
|
||||
}
|
||||
|
||||
# Checks for WAF. Honours $WAF if set. Stores path to 'waf' in $WAF.
|
||||
# Requires that $PYTHON is set.
|
||||
#
|
||||
checkWAF()
|
||||
{
|
||||
#installed miniwaf in sourcedir
|
||||
if [ -z "$WAF" ] ; then
|
||||
if [ -f "${WORKINGDIR}/build/waf" ] ; then
|
||||
WAF="${WORKINGDIR}/build/waf"
|
||||
if [ ! -x $WAF ] ; then
|
||||
chmod +x $WAF
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
#global installed waf with waf->waf.py link
|
||||
if [ -z $WAF ] ; then
|
||||
WAF=$(which waf 2>/dev/null)
|
||||
fi
|
||||
# neither waf nor miniwaf could be found
|
||||
if [ ! -x "$WAF" ] ; then
|
||||
printf $RED"not found"$NORMAL"\n"
|
||||
echo "Goto http://www.freehackers.org/~tnagy/bksys.html"
|
||||
echo "and download a waf version"
|
||||
exit $EXIT_FAILURE
|
||||
else
|
||||
printf $GREEN"$WAF"$NORMAL"\n"
|
||||
fi
|
||||
}
|
||||
|
||||
# Generates a Makefile. Requires that $WAF is set.
|
||||
#
|
||||
generateMakefile()
|
||||
{
|
||||
cat > Makefile << EOF
|
||||
#!/usr/bin/make -f
|
||||
|
||||
# Waf Makefile wrapper
|
||||
# http://www.freehackers.org/~tnagy/waf.html
|
||||
WAF_HOME=$CUR_DIR
|
||||
PYTHON=$PYTHON
|
||||
|
||||
all:
|
||||
@\$(PYTHON) $WAF build
|
||||
|
||||
all_debug:
|
||||
@\$(PYTHON) $WAF -v build
|
||||
|
||||
all_progress:
|
||||
@\$(PYTHON) $WAF -p build
|
||||
|
||||
install:
|
||||
@\$(PYTHON) $WAF install
|
||||
|
||||
uninstall:
|
||||
@\$(PYTHON) $WAF uninstall
|
||||
|
||||
clean:
|
||||
@\$(PYTHON) $WAF clean
|
||||
|
||||
distclean:
|
||||
@\$(PYTHON) $WAF distclean
|
||||
@-rm -rf cache/
|
||||
@-rm -rf _build_
|
||||
@-rm -f Makefile
|
||||
|
||||
check:
|
||||
@\$(PYTHON) $WAF check
|
||||
|
||||
dist:
|
||||
@\$(PYTHON) $WAF dist
|
||||
|
||||
test:
|
||||
@$TESTDIR/run_tests
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
checkPython
|
||||
checkWAF
|
||||
|
||||
echo "calling waf configure with parameters"
|
||||
$WAF configure $* || exit $EXIT_ERROR
|
||||
|
||||
#create a Makefile if waf configure succeeds
|
||||
if [ -f "${WORKINGDIR}/.lock-wscript" ] ; then
|
||||
generateMakefile
|
||||
fi
|
||||
exit $EXIT_SUCCESS
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
cola (1.0.0-1) unstable; urgency=low
|
||||
|
||||
Initial debian version
|
||||
|
||||
-- David Aguilar <davvid@gmail.com> Tue, 10 Jun 2008 12:00:00 -0700
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
5
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
Source: cola
|
||||
Section: devel
|
||||
Priority: extra
|
||||
Maintainer: David Aguilar <davvid@gmail.com>
|
||||
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: cola
|
||||
Architecture: all
|
||||
Depends: python (>= 2.4), python-qt4 (>= 4.3), git-core (>= 1.5), python-git (>= 0.3)
|
||||
Description: a highly caffeinated git gui
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
This package was debianized by David Aguilar <davvid@gmail.com> on
|
||||
Sun, 13 Apr 2008 18:28:41 -0700.
|
||||
|
||||
It was downloaded from http://gitcola.tuxfamily.org/releases
|
||||
|
||||
Upstream Author(s):
|
||||
|
||||
David Aguilar <davvid@gmail.com>
|
||||
|
||||
Copyright:
|
||||
|
||||
Copyright (C) 2008 David Aguilar
|
||||
|
||||
License:
|
||||
|
||||
This package is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as
|
||||
published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This package 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 Lesser General Public
|
||||
License along with this package; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License can be found in `/usr/share/common-licenses/LGPL'.
|
||||
|
||||
The Debian packaging is (C) 2008, David Aguilar <davvid@gmail.com> and
|
||||
is licensed under the GPL, see above.
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
usr/bin
|
||||
usr/share/applications
|
||||
usr/share/cola
|
||||
usr/share/cola/controllers
|
||||
usr/share/cola/views
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
# Sample debian/rules that uses debhelper.
|
||||
# This file was originally written by Joey Hess and Craig Small.
|
||||
# As a special exception, when this file is copied by dh-make into a
|
||||
# dh-make output file, you may use that output file without restriction.
|
||||
# This special exception was added by Craig Small in version 0.37 of dh-make.
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
DEBIAN=$(CURDIR)/debian
|
||||
BUILD=$(DEBIAN)/bld
|
||||
|
||||
configure: $(BUILD)/configure-stamp
|
||||
$(BUILD)/configure-stamp:
|
||||
./configure \
|
||||
--prefix=/usr \
|
||||
--blddir=$(BUILD) \
|
||||
--destdir=$(DEBIAN)/cola
|
||||
touch $(BUILD)/configure-stamp
|
||||
|
||||
build: $(BUILD)/build-stamp
|
||||
$(BUILD)/build-stamp: $(BUILD)/configure-stamp
|
||||
$(MAKE)
|
||||
touch $(BUILD)/build-stamp
|
||||
|
||||
clean:
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_clean
|
||||
rm -rf $(BUILD)
|
||||
|
||||
install: build
|
||||
|
||||
# Build architecture-independent files here.
|
||||
binary-indep: build install
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_clean -k
|
||||
dh_installdirs
|
||||
$(MAKE) DESTDIR=$(DEBIAN)/cola install
|
||||
dh_fixperms
|
||||
dh_installdeb
|
||||
dh_gencontrol
|
||||
dh_md5sums
|
||||
dh_builddeb
|
||||
|
||||
binary: binary-indep
|
||||
.PHONY: build clean binary-indep binary install configure
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
The following license applies to all icons except staged.png.
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
ACKNOWLEDGEMENTS:
|
||||
|
||||
staged.png is from the "slick" KDE icon set.
|
||||
All other icons are from the Apache httpd server icon set.
|
||||
See the COPYING file for more details.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 301 B |
Binary file not shown.
|
After Width: | Height: | Size: 202 B |
@@ -0,0 +1,2 @@
|
||||
*.msg
|
||||
*~
|
||||
@@ -0,0 +1,4 @@
|
||||
These files are from the git-gui sources.
|
||||
Please see git-gui's po/README file for more details.
|
||||
|
||||
These files are distributes under the same license as git-gui.
|
||||
@@ -0,0 +1,185 @@
|
||||
# Translation of git-gui glossary to German
|
||||
# Copyright (C) 2007 Shawn Pearce, et al.
|
||||
# This file is distributed under the same license as the git package.
|
||||
# Christian Stimming <stimming@tuhh.de>, 2007
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: git-gui glossary\n"
|
||||
"POT-Creation-Date: 2007-10-19 21:43+0200\n"
|
||||
"PO-Revision-Date: 2007-10-20 15:24+0200\n"
|
||||
"Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
|
||||
"Language-Team: German \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
|
||||
msgid ""
|
||||
"English Term (Dear translator: This file will never be visible to the user!)"
|
||||
msgstr ""
|
||||
"Deutsche Übersetzung.\n"
|
||||
"Andere deutsche SCM:\n"
|
||||
" http://tortoisesvn.net/docs/release/TortoiseSVN_de/index.html und http://"
|
||||
"tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_de.po "
|
||||
"(username=guest, password empty, gut),\n"
|
||||
" http://msdn.microsoft.com/de-de/library/ms181038(vs.80).aspx (MS Visual "
|
||||
"Source Safe, kommerziell),\n"
|
||||
" http://cvsbook.red-bean.com/translations/german/Kap_06.html "
|
||||
"(mittelmäßig),\n"
|
||||
" http://tortoisecvs.cvs.sourceforge.net/tortoisecvs/po/TortoiseCVS/de_DE.po?"
|
||||
"view=markup (mittelmäßig),\n"
|
||||
" http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/de/rapidsvn.po "
|
||||
"(username=guest, password empty, schlecht)"
|
||||
|
||||
#. ""
|
||||
msgid "amend"
|
||||
msgstr "nachbessern (ergänzen)"
|
||||
|
||||
#. ""
|
||||
msgid "annotate"
|
||||
msgstr "annotieren"
|
||||
|
||||
#. "A 'branch' is an active line of development."
|
||||
msgid "branch [noun]"
|
||||
msgstr "Zweig"
|
||||
|
||||
#. ""
|
||||
msgid "branch [verb]"
|
||||
msgstr "verzweigen"
|
||||
|
||||
#. ""
|
||||
msgid "checkout [noun]"
|
||||
msgstr ""
|
||||
"Arbeitskopie (Erstellung einer Arbeitskopie; Auscheck? Ausspielung? Abruf? "
|
||||
"Source Safe: Auscheckvorgang)"
|
||||
|
||||
#. "The action of updating the working tree to a revision which was stored in the object database."
|
||||
msgid "checkout [verb]"
|
||||
msgstr ""
|
||||
"Arbeitskopie erstellen; Zweig umstellen [checkout a branch] (auschecken? "
|
||||
"ausspielen? abrufen? Source Safe: auschecken)"
|
||||
|
||||
#. ""
|
||||
msgid "clone [verb]"
|
||||
msgstr "kopieren"
|
||||
|
||||
#. "A single point in the git history."
|
||||
msgid "commit [noun]"
|
||||
msgstr ""
|
||||
"Version; Eintragung; Änderung (Buchung?, Eintragung?, Übertragung?, "
|
||||
"Sendung?, Übergabe?, Einspielung?, Ablagevorgang?)"
|
||||
|
||||
#. "The action of storing a new snapshot of the project's state in the git history."
|
||||
msgid "commit [verb]"
|
||||
msgstr ""
|
||||
"eintragen (TortoiseSVN: übertragen; Source Safe: einchecken; senden?, "
|
||||
"übergeben?, einspielen?, einpflegen?, ablegen?)"
|
||||
|
||||
#. ""
|
||||
msgid "diff [noun]"
|
||||
msgstr "Vergleich (Source Safe: Unterschiede)"
|
||||
|
||||
#. ""
|
||||
msgid "diff [verb]"
|
||||
msgstr "vergleichen"
|
||||
|
||||
#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
|
||||
msgid "fast forward merge"
|
||||
msgstr "Schnellzusammenführung"
|
||||
|
||||
#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
|
||||
msgid "fetch"
|
||||
msgstr "anfordern (holen?)"
|
||||
|
||||
#. "A collection of files. The index is a stored version of your working tree."
|
||||
msgid "index (in git-gui: staging area)"
|
||||
msgstr "Bereitstellung"
|
||||
|
||||
#. "A successful merge results in the creation of a new commit representing the result of the merge."
|
||||
msgid "merge [noun]"
|
||||
msgstr "Zusammenführung"
|
||||
|
||||
#. "To bring the contents of another branch into the current branch."
|
||||
msgid "merge [verb]"
|
||||
msgstr "zusammenführen"
|
||||
|
||||
#. ""
|
||||
msgid "message"
|
||||
msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
|
||||
|
||||
#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
|
||||
msgid "prune"
|
||||
msgstr "entfernen"
|
||||
|
||||
#. "Pulling a branch means to fetch it and merge it."
|
||||
msgid "pull"
|
||||
msgstr "übernehmen (ziehen?)"
|
||||
|
||||
#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
|
||||
msgid "push"
|
||||
msgstr "versenden (ausliefern? hochladen? verschicken? schieben?)"
|
||||
|
||||
#. ""
|
||||
msgid "redo"
|
||||
msgstr "wiederholen"
|
||||
|
||||
#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
|
||||
msgid "remote"
|
||||
msgstr "Andere Archive (Gegenseite?, Entfernte?, Server?)"
|
||||
|
||||
#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
|
||||
msgid "repository"
|
||||
msgstr "Projektarchiv"
|
||||
|
||||
#. ""
|
||||
msgid "reset"
|
||||
msgstr "zurücksetzen (zurückkehren?)"
|
||||
|
||||
#. ""
|
||||
msgid "revert"
|
||||
msgstr "revidieren"
|
||||
|
||||
#. "A particular state of files and directories which was stored in the object database."
|
||||
msgid "revision"
|
||||
msgstr "Version (TortoiseSVN: Revision; Source Safe: Version)"
|
||||
|
||||
#. ""
|
||||
msgid "sign off"
|
||||
msgstr "abzeichnen (gegenzeichnen?, freizeichnen?, absegnen?)"
|
||||
|
||||
#. ""
|
||||
msgid "staging area"
|
||||
msgstr "Bereitstellung"
|
||||
|
||||
#. ""
|
||||
msgid "status"
|
||||
msgstr "Status"
|
||||
|
||||
#. "A ref pointing to a tag or commit object"
|
||||
msgid "tag [noun]"
|
||||
msgstr "Markierung"
|
||||
|
||||
#. ""
|
||||
msgid "tag [verb]"
|
||||
msgstr "markieren"
|
||||
|
||||
#. "A regular git branch that is used to follow changes from another repository."
|
||||
msgid "tracking branch"
|
||||
msgstr "Übernahmezweig"
|
||||
|
||||
#. ""
|
||||
msgid "undo"
|
||||
msgstr "rückgängig"
|
||||
|
||||
#. ""
|
||||
msgid "update"
|
||||
msgstr "aktualisieren"
|
||||
|
||||
#. ""
|
||||
msgid "verify"
|
||||
msgstr "überprüfen"
|
||||
|
||||
#. "The tree of actual checked out files."
|
||||
msgid "working copy, working tree"
|
||||
msgstr "Arbeitskopie"
|
||||
@@ -0,0 +1,184 @@
|
||||
# Translation of git-gui glossary to Italian
|
||||
# Copyright (C) 2007 Shawn Pearce, et al.
|
||||
# This file is distributed under the same license as the git package.
|
||||
# Christian Stimming <stimming@tuhh.de>, 2007
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: git-gui glossary\n"
|
||||
"POT-Creation-Date: 2007-10-19 21:43+0200\n"
|
||||
"PO-Revision-Date: 2007-10-10 15:24+0200\n"
|
||||
"Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n"
|
||||
"Language-Team: Italian \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
|
||||
msgid ""
|
||||
"English Term (Dear translator: This file will never be visible to the user!)"
|
||||
msgstr ""
|
||||
"Traduzione italiana.\n"
|
||||
"Altri SCM in italiano:\n"
|
||||
" http://tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_it."
|
||||
"po (username=guest, password empty),\n"
|
||||
" http://tortoisecvs.cvs.sourceforge.net/tortoisecvs/po/TortoiseCVS/it_IT.po?"
|
||||
"view=markup ,\n"
|
||||
" http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/it_IT/rapidsvn.po "
|
||||
"(username=guest, password empty)"
|
||||
|
||||
#. ""
|
||||
msgid "amend"
|
||||
msgstr "correggere, correzione"
|
||||
|
||||
#. ""
|
||||
msgid "annotate"
|
||||
msgstr "annotare, annotazione"
|
||||
|
||||
#. "A 'branch' is an active line of development."
|
||||
msgid "branch [noun]"
|
||||
msgstr "ramo, diramazione, ramificazione"
|
||||
|
||||
#. ""
|
||||
msgid "branch [verb]"
|
||||
msgstr "creare ramo, ramificare, diramare"
|
||||
|
||||
#. ""
|
||||
msgid "checkout [noun]"
|
||||
msgstr "attivazione, checkout, revisione attiva, prelievo (TortoiseCVS)?"
|
||||
|
||||
#. "The action of updating the working tree to a revision which was stored in the object database."
|
||||
msgid "checkout [verb]"
|
||||
msgstr ""
|
||||
"attivare, effettuare un checkout, attivare revisione, prelevare "
|
||||
"(TortoiseCVS), ritirare (TSVN)?"
|
||||
|
||||
#. ""
|
||||
msgid "clone [verb]"
|
||||
msgstr "clonare"
|
||||
|
||||
#. "A single point in the git history."
|
||||
msgid "commit [noun]"
|
||||
msgstr "revisione, commit, deposito (TortoiseCVS), invio (TSVN)?"
|
||||
|
||||
#. "The action of storing a new snapshot of the project's state in the git history."
|
||||
msgid "commit [verb]"
|
||||
msgstr ""
|
||||
"creare una nuova revisione, archiviare, effettuare un commit, depositare "
|
||||
"(nel server), fare un deposito (TortoiseCVS), inviare (TSVN)?"
|
||||
|
||||
#. ""
|
||||
msgid "diff [noun]"
|
||||
msgstr "differenza, confronto, comparazione, raffronto"
|
||||
|
||||
#. ""
|
||||
msgid "diff [verb]"
|
||||
msgstr "confronta, mostra le differenze"
|
||||
|
||||
#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
|
||||
msgid "fast forward merge"
|
||||
msgstr "fusione in 'fast-forward', fusione in avanti veloce"
|
||||
|
||||
#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
|
||||
msgid "fetch"
|
||||
msgstr "recuperare, prelevare, prendere da, recuperare (TSVN)"
|
||||
|
||||
#. "A collection of files. The index is a stored version of your working tree."
|
||||
msgid "index (in git-gui: staging area)"
|
||||
msgstr "indice"
|
||||
|
||||
#. "A successful merge results in the creation of a new commit representing the result of the merge."
|
||||
msgid "merge [noun]"
|
||||
msgstr "fusione, unione"
|
||||
|
||||
#. "To bring the contents of another branch into the current branch."
|
||||
msgid "merge [verb]"
|
||||
msgstr "effettuare la fusione, unire, fondere, eseguire la fusione"
|
||||
|
||||
#. ""
|
||||
msgid "message"
|
||||
msgstr "messaggio, commento"
|
||||
|
||||
#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
|
||||
msgid "prune"
|
||||
msgstr "potatura"
|
||||
|
||||
#. "Pulling a branch means to fetch it and merge it."
|
||||
msgid "pull"
|
||||
msgstr ""
|
||||
"prendi (recupera) e fondi (unisci)? (in pratica una traduzione di fetch + "
|
||||
"merge)"
|
||||
|
||||
#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
|
||||
msgid "push"
|
||||
msgstr "propaga"
|
||||
|
||||
#. ""
|
||||
msgid "redo"
|
||||
msgstr "ripeti, rifai"
|
||||
|
||||
#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
|
||||
msgid "remote"
|
||||
msgstr "remoto"
|
||||
|
||||
#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
|
||||
msgid "repository"
|
||||
msgstr "archivio, repository, database? deposito (rapidsvn)?"
|
||||
|
||||
#. ""
|
||||
msgid "reset"
|
||||
msgstr "ripristinare, annullare, azzerare, ripristinare"
|
||||
|
||||
#. ""
|
||||
msgid "revert"
|
||||
msgstr ""
|
||||
"annullare, inverti (rapidsvn), ritorna allo stato precedente, annulla le "
|
||||
"modifiche della revisione"
|
||||
|
||||
#. "A particular state of files and directories which was stored in the object database."
|
||||
msgid "revision"
|
||||
msgstr "revisione (TortoiseSVN)"
|
||||
|
||||
#. ""
|
||||
msgid "sign off"
|
||||
msgstr "sign off, firma"
|
||||
|
||||
#. ""
|
||||
msgid "staging area"
|
||||
msgstr ""
|
||||
"area di preparazione, zona di preparazione, modifiche in preparazione? "
|
||||
"modifiche in allestimento?"
|
||||
|
||||
#. ""
|
||||
msgid "status"
|
||||
msgstr "stato"
|
||||
|
||||
#. "A ref pointing to a tag or commit object"
|
||||
msgid "tag [noun]"
|
||||
msgstr "etichetta, etichettatura (TortoiseCVS)"
|
||||
|
||||
#. ""
|
||||
msgid "tag [verb]"
|
||||
msgstr "etichettare"
|
||||
|
||||
#. "A regular git branch that is used to follow changes from another repository."
|
||||
msgid "tracking branch"
|
||||
msgstr ""
|
||||
"duplicato locale di ramo remoto, ramo in 'tracking', ramo inseguitore? ramo "
|
||||
"di {inseguimento,allineamento,rilevamento,puntamento}?"
|
||||
|
||||
#. ""
|
||||
msgid "undo"
|
||||
msgstr "annulla"
|
||||
|
||||
#. ""
|
||||
msgid "update"
|
||||
msgstr "aggiornamento, aggiornare"
|
||||
|
||||
#. ""
|
||||
msgid "verify"
|
||||
msgstr "verifica, verificare"
|
||||
|
||||
#. "The tree of actual checked out files."
|
||||
msgid "working copy, working tree"
|
||||
msgstr "directory di lavoro, copia di lavoro"
|
||||
@@ -0,0 +1,170 @@
|
||||
# Translation of git-gui glossary to Simplified Chinese
|
||||
# Copyright (C) 2007 Shawn Pearce, et al.
|
||||
# This file is distributed under the same license as the git package.
|
||||
# Xudong Guan <xudong.guan@gmail.com> and the zh-kernel.org mailing list, 2007
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: git-gui glossary\n"
|
||||
"PO-Revision-Date: 2007-07-23 22:07+0200\n"
|
||||
"Last-Translator: Xudong Guan <xudong.guan@gmail.com>\n"
|
||||
"Language-Team: Simplified Chinese \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
|
||||
msgid ""
|
||||
"English Term (Dear translator: This file will never be visible to the user!)"
|
||||
msgstr "注:这个文件是为了帮助翻译人员统一名词术语。最终用户不会关心这个文件。"
|
||||
|
||||
#. ""
|
||||
#. amend指用户修改最近一次commit的操作,修订?修改?修正?
|
||||
#. [WANG Cong]: 根据我的了解,这个词似乎翻译成“修订”多一些。“修正”也可以,“修改”再次之。
|
||||
#. [ZHANG Le]: 修订,感觉一般指对一些大型出版物的大规模升级,比如修订新华字典
|
||||
# 修正,其实每次amend的结果也不一定就是最后结果,说不定还需要修改。所以不
|
||||
# 如就叫修改
|
||||
msgid "amend"
|
||||
msgstr "修订"
|
||||
|
||||
#. ""
|
||||
#. git annotate 文件名:用来标注文件的每一行在什么时候被谁最后修改。
|
||||
#. [WANG Cong]: "标记"一般是mark。;)
|
||||
#. [ZHANG Le]: 标注,或者干脆用原意:注解,或注释
|
||||
msgid "annotate"
|
||||
msgstr "标注"
|
||||
|
||||
#. "A 'branch' is an active line of development."
|
||||
msgid "branch [noun]"
|
||||
msgstr "分支"
|
||||
|
||||
#. ""
|
||||
msgid "branch [verb]"
|
||||
msgstr "建立分支"
|
||||
|
||||
#. ""
|
||||
#. [WANG Cong]: 网上有人翻译成“检出”,我感觉更好一些,毕竟把check的意思翻译出来了。
|
||||
#. [ZHNAG Le]: 提取吧,提取分支/版本
|
||||
#. [rae l]: 签出。subversion软件中的大多词汇已有翻译,既然git与subversion同是SCM管理,可以参考同类软件的翻译也不错。
|
||||
msgid "checkout [noun]"
|
||||
msgstr "签出"
|
||||
|
||||
#. "The action of updating the working tree to a revision which was stored in the object database."
|
||||
msgid "checkout [verb]"
|
||||
msgstr "签出"
|
||||
|
||||
#. "A single point in the git history."
|
||||
msgid "commit [noun]"
|
||||
msgstr "提交"
|
||||
|
||||
#. "The action of storing a new snapshot of the project's state in the git history."
|
||||
msgid "commit [verb]"
|
||||
msgstr "提交"
|
||||
|
||||
#. ""
|
||||
#. 差异?差别?
|
||||
#. [ZHANG Le]: 个人感觉差别更加中性一些
|
||||
msgid "diff [noun]"
|
||||
msgstr "差别"
|
||||
|
||||
#. ""
|
||||
msgid "diff [verb]"
|
||||
msgstr "比较"
|
||||
|
||||
#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
|
||||
msgid "fast forward merge"
|
||||
msgstr "快进式合并"
|
||||
|
||||
#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
|
||||
#. 获取?取得?下载?更新?注意和update的区分
|
||||
msgid "fetch"
|
||||
msgstr "获取"
|
||||
|
||||
#. "A collection of files. The index is a stored version of your working tree."
|
||||
#. index是working tree和repository之间的缓存
|
||||
msgid "index (in git-gui: staging area)"
|
||||
msgstr "工作缓存?"
|
||||
|
||||
#. "A successful merge results in the creation of a new commit representing the result of the merge."
|
||||
msgid "merge [noun]"
|
||||
msgstr "合并"
|
||||
|
||||
#. "To bring the contents of another branch into the current branch."
|
||||
msgid "merge [verb]"
|
||||
msgstr "合并"
|
||||
|
||||
#. ""
|
||||
#. message是指commit中的文字信息
|
||||
msgid "message"
|
||||
msgstr "描述"
|
||||
|
||||
#. "Pulling a branch means to fetch it and merge it."
|
||||
msgid "pull"
|
||||
msgstr "获取+合并"
|
||||
|
||||
#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
|
||||
msgid "push"
|
||||
msgstr "推入"
|
||||
|
||||
#. ""
|
||||
msgid "redo"
|
||||
msgstr "重做"
|
||||
|
||||
#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
|
||||
msgid "repository"
|
||||
msgstr "仓库"
|
||||
|
||||
#. ""
|
||||
msgid "reset"
|
||||
msgstr "重置"
|
||||
|
||||
#. ""
|
||||
msgid "revert"
|
||||
msgstr "恢复"
|
||||
|
||||
#. "A particular state of files and directories which was stored in the object database."
|
||||
msgid "revision"
|
||||
msgstr "版本"
|
||||
|
||||
#. ""
|
||||
msgid "sign off"
|
||||
msgstr "签名"
|
||||
|
||||
#. ""
|
||||
#. 似乎是git-gui里面显示的本次提交的文件清单区域
|
||||
msgid "staging area"
|
||||
msgstr "提交暂存区"
|
||||
|
||||
#. ""
|
||||
msgid "status"
|
||||
msgstr "状态"
|
||||
|
||||
#. "A ref pointing to a tag or commit object"
|
||||
msgid "tag [noun]"
|
||||
msgstr "标签"
|
||||
|
||||
#. ""
|
||||
msgid "tag [verb]"
|
||||
msgstr "添加标签"
|
||||
|
||||
#. "A regular git branch that is used to follow changes from another repository."
|
||||
msgid "tracking branch"
|
||||
msgstr "跟踪分支"
|
||||
|
||||
#. ""
|
||||
msgid "undo"
|
||||
msgstr "撤销"
|
||||
|
||||
#. ""
|
||||
msgid "update"
|
||||
msgstr "更新。注意和fetch的区分"
|
||||
|
||||
#. ""
|
||||
msgid "verify"
|
||||
msgstr "验证"
|
||||
|
||||
#. "The tree of actual checked out files."
|
||||
#. "工作副本?工作区域?工作目录"
|
||||
#. [LI Yang]: 当前副本, 当前源码树?
|
||||
msgid "working copy, working tree"
|
||||
msgstr "工作副本,工作源码树"
|
||||
+1769
File diff suppressed because it is too large
Load Diff
-252
@@ -1,252 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import commands
|
||||
import ugitutils
|
||||
|
||||
def git_add (to_add):
|
||||
'''Invokes 'git add' to index the filenames in to_add.'''
|
||||
|
||||
if not to_add: return 'ERROR: No files to add.'
|
||||
|
||||
argv = [ 'git', 'add' ]
|
||||
for filename in to_add:
|
||||
argv.append (ugitutils.shell_quote (filename))
|
||||
|
||||
cmd = ' '.join (argv)
|
||||
return 'Running:\t%s\n%s\n%s added successfully' % (
|
||||
cmd, commands.getoutput (cmd), ', '.join (to_add) )
|
||||
|
||||
def git_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 'ERROR: No files to add or remove.'
|
||||
|
||||
to_add = []
|
||||
output = ''
|
||||
|
||||
for filename in to_process:
|
||||
if os.path.exists (filename):
|
||||
to_add.append (filename)
|
||||
|
||||
if to_add:
|
||||
output += git_add (to_add) + '\n\n'
|
||||
|
||||
if len(to_add) == len(to_process):
|
||||
# to_process only contained unremoved files --
|
||||
# short-circuit the removal checks
|
||||
return output
|
||||
|
||||
# Process files to add
|
||||
argv = [ 'git', 'rm' ]
|
||||
for filename in to_process:
|
||||
if not os.path.exists (filename):
|
||||
argv.append (ugitutils.shell_quote (filename))
|
||||
|
||||
cmd = ' '.join (argv)
|
||||
return output + 'Running: %s\n%s' % ( cmd, commands.getoutput (cmd) )
|
||||
|
||||
def git_branch():
|
||||
'''Returns 'git branch''s output in a list.'''
|
||||
return commands.getoutput ('git branch').split ('\n')
|
||||
|
||||
def git_cherry_pick (revs, commit=False):
|
||||
'''Cherry-picks each revision into the current branch.'''
|
||||
if not revs:
|
||||
return 'ERROR: No revisions selected for cherry-picking.'''
|
||||
|
||||
cmd = 'git cherry-pick '
|
||||
if not commit: cmd += '-n '
|
||||
output = []
|
||||
for rev in revs:
|
||||
output.append ('Cherry-picking: ' + rev)
|
||||
output.append (commands.getoutput (cmd + rev))
|
||||
output.append ('')
|
||||
return '\n'.join (output)
|
||||
|
||||
def git_commit (msg, amend, commit_all, files):
|
||||
'''Creates a git commit. 'commit_all' triggers the -a
|
||||
flag to 'git commit.' 'amend' triggers --amend.
|
||||
'files' is a list of files to use for commits without -a.'''
|
||||
|
||||
if not msg:
|
||||
return 'ERROR: No commit message was provided.'
|
||||
|
||||
# Allow TMPDIR/TMP with a fallback to /tmp
|
||||
tmpdir = os.getenv ('TMPDIR', os.getenv ('TMP', '/tmp'))
|
||||
|
||||
# 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 = os.path.join (tmpdir,
|
||||
'ugit.%s.%s' % ( os.getuid(), time.time() ))
|
||||
|
||||
argv = [ 'git', 'commit', '-F', tmpfile ]
|
||||
|
||||
if amend: argv.append ('--amend')
|
||||
|
||||
if commit_all:
|
||||
argv.append ('-a')
|
||||
else:
|
||||
if not files:
|
||||
return 'ERROR: No files selected for commit.'
|
||||
|
||||
argv.append ('--')
|
||||
for file in files:
|
||||
argv.append (ugitutils.shell_quote (file))
|
||||
|
||||
# Create the commit message file
|
||||
file = open (tmpfile, 'w')
|
||||
file.write (msg)
|
||||
file.close()
|
||||
|
||||
# Run 'git commit'
|
||||
cmd = ' '.join (argv)
|
||||
output = commands.getoutput (cmd)
|
||||
os.unlink (tmpfile)
|
||||
|
||||
return 'Running:\t%s\n%s' % ( cmd, output )
|
||||
|
||||
def git_current_branch():
|
||||
'''Parses 'git branch' to find the current branch.'''
|
||||
for branch in git_branch():
|
||||
if branch.startswith ('* '):
|
||||
return branch.lstrip ('* ')
|
||||
raise Exception, 'No current branch. Detached HEAD?'
|
||||
|
||||
def git_diff (filename, staged=True):
|
||||
'''Invokes git_diff on filename. Passing staged=True adds
|
||||
diffs the index against HEAD (i.e. --cached).'''
|
||||
|
||||
deleted = False
|
||||
argv = [ 'git', 'diff', '--color']
|
||||
if staged:
|
||||
deleted = not os.path.exists (filename)
|
||||
argv.append ('--cached')
|
||||
|
||||
argv.append ('--')
|
||||
argv.append (ugitutils.shell_quote (filename))
|
||||
|
||||
diff = commands.getoutput (' '.join (argv))
|
||||
diff_lines = diff.split ('\n')
|
||||
|
||||
output = []
|
||||
start = False
|
||||
del_tag = 'deleted file mode '
|
||||
|
||||
for line in diff_lines:
|
||||
if not start and '@@ ' in line and ' @@' in line:
|
||||
start = True
|
||||
if start or (deleted and del_tag in line):
|
||||
output.append (line)
|
||||
return '\n'.join (output)
|
||||
|
||||
def git_diff_stat ():
|
||||
'''Returns the latest diffstat.'''
|
||||
return commands.getoutput ('git diff --color --stat HEAD^')
|
||||
|
||||
def git_format_patch (revs, use_range):
|
||||
'''Exports patches revs in the 'ugit-patches' subdirectory.
|
||||
If use_range is True, a commit range is passed to git format-patch.'''
|
||||
|
||||
cmd = 'git format-patch --thread --patch-with-stat -o ugit-patches '
|
||||
header = 'Generated Patches:'
|
||||
if len (revs) > 1:
|
||||
cmd += '-n '
|
||||
|
||||
if use_range:
|
||||
rev_range = '%s^..%s' % ( revs[-1], revs[0] )
|
||||
return header + '\n' + commands.getoutput (cmd + rev_range)
|
||||
|
||||
output = [ header ]
|
||||
num_patches = 1
|
||||
for idx, rev in enumerate (revs):
|
||||
real_idx = idx + num_patches
|
||||
revcmd = cmd + '-1 --start-number %d %s' % (real_idx, rev)
|
||||
output.append (commands.getoutput (revcmd))
|
||||
num_patches += output[-1].count ('\n')
|
||||
return '\n'.join (output)
|
||||
|
||||
def git_log (oneline=True, all=False):
|
||||
'''Returns a pair of parallel arrays listing the revision sha1's
|
||||
and commit summaries.'''
|
||||
argv = [ 'git', 'log' ]
|
||||
if oneline: argv.append ('--pretty=oneline')
|
||||
if all: argv.append ('--all')
|
||||
revs = []
|
||||
summaries = []
|
||||
regex = re.compile ('(\w+)\W(.*)')
|
||||
output = commands.getoutput (' '.join (argv))
|
||||
for line in output.split ('\n'):
|
||||
match = regex.match (line)
|
||||
if match:
|
||||
revs.append (match.group (1))
|
||||
summaries.append (match.group (2))
|
||||
return ( revs, summaries )
|
||||
|
||||
def git_reset (to_unstage):
|
||||
'''Use 'git reset' to unstage files from the index.'''
|
||||
|
||||
if not to_unstage: return 'ERROR: No files to reset.'
|
||||
|
||||
argv = [ 'git', 'reset', '--' ]
|
||||
for filename in to_unstage:
|
||||
argv.append (ugitutils.shell_quote (filename))
|
||||
|
||||
cmd = ' '.join (argv)
|
||||
return 'Running:\t%s\n%s' % ( cmd, commands.getoutput (cmd) )
|
||||
|
||||
def git_show (sha1, color=False):
|
||||
cmd = 'git show '
|
||||
if color: cmd += '--color '
|
||||
return commands.getoutput (cmd + sha1)
|
||||
|
||||
def git_show_cdup():
|
||||
'''Returns a relative path to the git project root.'''
|
||||
return commands.getoutput ('git rev-parse --show-cdup')
|
||||
|
||||
def git_status():
|
||||
'''RETURNS: A tuple of staged, unstaged and untracked files.
|
||||
( array(staged), array(unstaged), array(untracked) )'''
|
||||
|
||||
status_lines = commands.getoutput ('git status').split ('\n')
|
||||
|
||||
unstaged_header_seen = False
|
||||
untracked_header_seen = False
|
||||
|
||||
modified_header = '# Changed but not updated:'
|
||||
modified_regex = re.compile('(#\tmodified:|#\tnew file:|#\tdeleted:)')
|
||||
|
||||
untracked_header = '# Untracked files:'
|
||||
untracked_regex = re.compile ('#\t(.+)')
|
||||
|
||||
staged = []
|
||||
unstaged = []
|
||||
untracked = []
|
||||
|
||||
for status_line in status_lines:
|
||||
if untracked_header in status_line:
|
||||
untracked_header_seen = True
|
||||
continue
|
||||
if not untracked_header_seen:
|
||||
continue
|
||||
match = untracked_regex.match (status_line)
|
||||
if match:
|
||||
filename = match.group (1)
|
||||
untracked.append (filename)
|
||||
|
||||
for status_line in status_lines:
|
||||
if modified_header in status_line:
|
||||
unstaged_header_seen = True
|
||||
continue
|
||||
match = modified_regex.match (status_line)
|
||||
if match:
|
||||
tag = match.group (0)
|
||||
filename = status_line.replace (tag, '')
|
||||
if unstaged_header_seen:
|
||||
unstaged.append (filename.lstrip())
|
||||
else:
|
||||
staged.append (filename.lstrip())
|
||||
|
||||
return ( staged, unstaged, untracked )
|
||||
@@ -1,515 +0,0 @@
|
||||
import os
|
||||
import commands
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.QtCore import QObject
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
from PyQt4.QtGui import QClipboard
|
||||
from PyQt4.QtGui import QDialog
|
||||
from PyQt4.QtGui import QPixmap
|
||||
from PyQt4.QtGui import QIcon
|
||||
from PyQt4.QtGui import QListWidgetItem
|
||||
from qobserver import QObserver
|
||||
import ugitcmds
|
||||
import ugitutils
|
||||
from ugitview import GitCommandDialog
|
||||
from ugitview import GitCommitBrowser
|
||||
|
||||
class GitController (QObserver):
|
||||
'''The controller is a mediator between the model and view.
|
||||
It allows for a clean decoupling between view and model classes.'''
|
||||
|
||||
def __init__ (self, model, view):
|
||||
QObserver.__init__ (self, model, view)
|
||||
|
||||
# Register ourselves with the model
|
||||
model.add_observer (self)
|
||||
|
||||
# Binds a specific model attribute to a view widget,
|
||||
# and vice versa.
|
||||
self.model_to_view (model, 'commitmsg', 'commitText')
|
||||
|
||||
# When a model attribute changes, this runs a specific action
|
||||
self.add_actions (model, 'staged', self.action_staged)
|
||||
self.add_actions (model, 'unstaged', self.action_unstaged)
|
||||
self.add_actions (model, 'untracked', self.action_unstaged)
|
||||
|
||||
# Routes signals for multiple widgets to our callbacks
|
||||
# defined below.
|
||||
self.add_signals ('textChanged()', view.commitText)
|
||||
self.add_signals ('stateChanged(int)', view.untrackedCheckBox)
|
||||
|
||||
self.add_signals ('pressed()',
|
||||
view.rescanButton,
|
||||
view.stageButton,
|
||||
view.commitButton,
|
||||
view.pushButton,
|
||||
view.signOffButton,)
|
||||
|
||||
self.add_signals ('triggered()',
|
||||
view.rescan,
|
||||
view.commitAll,
|
||||
view.commitSelected,
|
||||
view.getCommitMessage,
|
||||
view.stageChanged,
|
||||
view.stageUntracked,
|
||||
view.stageSelected,
|
||||
view.unstageAll,
|
||||
view.unstageSelected,
|
||||
view.showDiffstat,
|
||||
view.browseBranch,
|
||||
view.browseOtherBranch,
|
||||
view.visualizeAll,
|
||||
view.visualizeCurrent,
|
||||
view.exportPatches,
|
||||
view.cherryPick,)
|
||||
|
||||
self.add_signals ('itemSelectionChanged()',
|
||||
view.stagedList,
|
||||
view.unstagedList,)
|
||||
|
||||
# Handle double-clicks in the staged/unstaged lists.
|
||||
# These are vanilla signal/slots since the qobserver
|
||||
# signal routing is already handling these lists' signals.
|
||||
QObject.connect ( view.unstagedList,
|
||||
SIGNAL ('itemDoubleClicked(QListWidgetItem*)'),
|
||||
lambda (x): self.cb_stage_selected (model) )
|
||||
|
||||
QObject.connect ( view.stagedList,
|
||||
SIGNAL ('itemDoubleClicked(QListWidgetItem*)'),
|
||||
lambda (x): self.cb_unstage_selected (model) )
|
||||
|
||||
# These callbacks are called in response to the signals
|
||||
# defined above. One property of the QObserver callback
|
||||
# mechanism is that the model is passed in as the first
|
||||
# argument to the callback. This allows for a single
|
||||
# controller to manage multiple models, though this
|
||||
# isn't used at the moment.
|
||||
self.add_callbacks (model, {
|
||||
# Push Buttons
|
||||
'rescanButton': self.cb_rescan,
|
||||
'stageButton': self.cb_stage_selected,
|
||||
'signOffButton': self.cb_signoff,
|
||||
'commitButton': self.cb_commit,
|
||||
# Checkboxes
|
||||
'untrackedCheckBox': self.cb_rescan,
|
||||
# List Widgets
|
||||
'stagedList': self.cb_diff_staged,
|
||||
'unstagedList': self.cb_diff_unstaged,
|
||||
# Menu Actions
|
||||
'rescan': self.cb_rescan,
|
||||
'commitAll': self.cb_commit_all,
|
||||
'commitSelected': self.cb_commit_selected,
|
||||
'getCommitMessage': self.cb_get_commit_msg,
|
||||
'stageChanged': self.cb_stage_changed,
|
||||
'stageUntracked': self.cb_stage_untracked,
|
||||
'stageSelected': self.cb_stage_selected,
|
||||
'unstageAll': self.cb_unstage_all,
|
||||
'unstageSelected': self.cb_unstage_selected,
|
||||
'showDiffstat': self.cb_show_diffstat,
|
||||
'browseBranch': self.cb_browse_current,
|
||||
'browseOtherBranch': self.cb_browse_other,
|
||||
'visualizeCurrent': self.cb_viz_current,
|
||||
'visualizeAll': self.cb_viz_all,
|
||||
'exportPatches': self.cb_export_patches,
|
||||
'cherryPick': self.cb_cherry_pick,
|
||||
})
|
||||
|
||||
# chdir to the root of the git tree. This is critical
|
||||
# to being able to properly use the git porcelain.
|
||||
cdup = ugitcmds.git_show_cdup()
|
||||
if cdup: os.chdir (cdup)
|
||||
|
||||
# Default to creating a new commit (i.e. not an amend commit)
|
||||
view.newCommitRadio.setChecked (True)
|
||||
|
||||
self.__set_branch_ui_items()
|
||||
self.cb_rescan (model)
|
||||
|
||||
#####################################################################
|
||||
# MODEL ACTIONS
|
||||
#####################################################################
|
||||
|
||||
def action_staged (self, model, *args):
|
||||
'''This action is called when the model's staged list
|
||||
changes. This is a thin wrapper around update_list_widget.'''
|
||||
list_widget = self.view.stagedList
|
||||
staged = model.get_staged()
|
||||
self.__update_list_widget (list_widget, staged, True)
|
||||
|
||||
def action_unstaged (self, model, *args):
|
||||
'''This action is called when the model's unstaged list
|
||||
changes. This is a thin wrapper around update_list_widget.'''
|
||||
list_widget = self.view.unstagedList
|
||||
unstaged = model.get_unstaged()
|
||||
self.__update_list_widget (list_widget, unstaged, False)
|
||||
if self.view.untrackedCheckBox.isChecked():
|
||||
untracked = model.get_untracked()
|
||||
self.__update_list_widget (list_widget, untracked,
|
||||
append=True,
|
||||
staged=False,
|
||||
untracked=True)
|
||||
|
||||
#####################################################################
|
||||
# CALLBACKS
|
||||
#####################################################################
|
||||
|
||||
def cb_browse_current (self, model):
|
||||
print "MODEL::::::::::", model
|
||||
|
||||
def cb_browse_other (self, model):
|
||||
print "MODEL::::::::::", model
|
||||
|
||||
def cb_cherry_pick (self, model, *args):
|
||||
'''Starts a cherry-picking session.'''
|
||||
(revs, summaries) = ugitcmds.git_log (all=True)
|
||||
selection, idxs = self.__select_commits (revs, summaries)
|
||||
if not selection: return
|
||||
|
||||
output = ugitcmds.git_cherry_pick (selection)
|
||||
self.__show_command_output (output, model)
|
||||
|
||||
def cb_commit (self, model, *args):
|
||||
'''Sets up data and calls ugitcmds.commit.'''
|
||||
|
||||
msg = model.get_commitmsg()
|
||||
amend = self.view.amendRadio.isChecked()
|
||||
commit_all = self.view.commitAllCheckBox.isChecked()
|
||||
|
||||
files = []
|
||||
if not commit_all:
|
||||
files = self.__get_selection_from_view (
|
||||
self.view.stagedList,
|
||||
model.get_staged() )
|
||||
# Perform the commit
|
||||
output = ugitcmds.git_commit (msg, amend, commit_all, files)
|
||||
|
||||
# Reset the commitmsg and rescan changes
|
||||
if not output.startswith ('ERROR'):
|
||||
model.set_commitmsg ('')
|
||||
|
||||
self.__show_command_output (output, model)
|
||||
|
||||
def cb_commit_all (self, model, *args):
|
||||
'''Sets the commit-all checkbox and runs cb_commit.'''
|
||||
self.view.commitAllCheckBox.setChecked (True)
|
||||
self.cb_commit (model, *args)
|
||||
|
||||
def cb_commit_selected (self, model, *args):
|
||||
'''Unsets the commit-all checkbox and runs cb_commit.'''
|
||||
self.view.commitAllCheckBox.setChecked (False)
|
||||
self.cb_commit (model, *args)
|
||||
|
||||
def cb_commit_sha1_selected (self, browser, revs):
|
||||
'''This callback is called when a commit browser's
|
||||
item is selected. This callback puts the current
|
||||
revision sha1 into the commitText field.
|
||||
This callback also puts shows the commit in the
|
||||
browser's commit textedit and copies it into
|
||||
the global clipboard/selection.'''
|
||||
current = browser.commitList.currentRow()
|
||||
item = browser.commitList.item (current)
|
||||
if not item.isSelected():
|
||||
browser.commitText.setText ('')
|
||||
browser.revisionLine.setText ('')
|
||||
return
|
||||
|
||||
# Get the commit's sha1 and put it in the revision line
|
||||
sha1 = revs[current]
|
||||
browser.revisionLine.setText (sha1)
|
||||
browser.revisionLine.selectAll()
|
||||
|
||||
# Lookup the info for that sha1 and display it
|
||||
commit_info = ugitcmds.git_show (sha1, color=True)
|
||||
html = ugitutils.ansi_to_html (commit_info)
|
||||
browser.commitText.setText (html)
|
||||
|
||||
# Copy the sha1 into the clipboard
|
||||
QtGui.qApp.clipboard().setText (sha1, QClipboard.Clipboard)
|
||||
QtGui.qApp.clipboard().setText (sha1, QClipboard.Selection)
|
||||
|
||||
def cb_diff_staged (self, model):
|
||||
list_widget = self.view.stagedList
|
||||
row, selected = self.__get_selected_row (list_widget)
|
||||
|
||||
if not selected:
|
||||
self.view.displayText.setText ('')
|
||||
return
|
||||
|
||||
filename = model.get_staged()[row]
|
||||
diff = ugitcmds.git_diff (filename, staged=True)
|
||||
html = ugitutils.ansi_to_html (diff)
|
||||
|
||||
if os.path.exists (filename):
|
||||
pre = ugitutils.html_header ('Staged for commit')
|
||||
else:
|
||||
pre = ugitutils.html_header ('Staged for removal')
|
||||
|
||||
self.view.displayText.setText (pre + html)
|
||||
|
||||
def cb_diff_unstaged (self, model):
|
||||
list_widget = self.view.unstagedList
|
||||
row, selected = self.__get_selected_row (list_widget)
|
||||
if not selected:
|
||||
self.view.displayText.setText ('')
|
||||
return
|
||||
filename = (model.get_unstaged() + model.get_untracked())[row]
|
||||
if os.path.isdir (filename):
|
||||
pre = ugitutils.html_header ('Untracked directory')
|
||||
cmd = 'ls -la %s' % ugitutils.shell_quote (filename)
|
||||
html = '<pre>%s</pre>' % commands.getoutput (cmd)
|
||||
self.view.displayText.setText ( pre + html )
|
||||
return
|
||||
|
||||
if filename in model.get_unstaged():
|
||||
diff = ugitcmds.git_diff (filename, staged=False)
|
||||
pre = ugitutils.html_header ('Modified, unstaged')
|
||||
html = ugitutils.ansi_to_html (diff)
|
||||
else:
|
||||
# untracked file
|
||||
cmd = 'file -b %s' % ugitutils.shell_quote (filename)
|
||||
file_type = commands.getoutput (cmd)
|
||||
if 'binary' in file_type or 'data' in file_type:
|
||||
sq_filename = ugitutils.shell_quote (filename)
|
||||
cmd = 'hexdump -C %s' % sq_filename
|
||||
contents = commands.getoutput (cmd)
|
||||
else:
|
||||
file = open (filename, 'r')
|
||||
contents = ugitutils.html_encode (file.read())
|
||||
file.close()
|
||||
|
||||
header = 'Untracked file: ' + file_type
|
||||
pre = ugitutils.html_header (header)
|
||||
html = '<pre>%s</pre>' % contents
|
||||
|
||||
self.view.displayText.setText (pre + html)
|
||||
|
||||
def cb_export_patches (self, model, *args):
|
||||
'''Launches the commit browser and exports the selected
|
||||
patches.'''
|
||||
|
||||
(revs, summaries) = ugitcmds.git_log ()
|
||||
selection, idxs = self.__select_commits (revs, summaries)
|
||||
if not selection: return
|
||||
|
||||
# now get the selected indices to determine whether
|
||||
# a range of consecutive commits were selected
|
||||
selected_range = range (idxs[0], idxs[-1] + 1)
|
||||
export_range = len (idxs) > 1 and idxs == selected_range
|
||||
|
||||
output = ugitcmds.git_format_patch (selection, export_range)
|
||||
self.__show_command_output (output)
|
||||
|
||||
def cb_get_commit_msg (self, model, *args):
|
||||
(revs, summaries) = ugitcmds.git_log (oneline=True)
|
||||
commit_msg = []
|
||||
commit_lines = ugitcmds.git_show (revs[0]).split ('\n')
|
||||
for idx, msg in enumerate (commit_lines):
|
||||
if idx < 4: continue
|
||||
msg = msg.lstrip()
|
||||
if msg.startswith ('diff --git'):
|
||||
commit_msg.pop()
|
||||
break
|
||||
commit_msg.append (msg)
|
||||
model.set_commitmsg ('\n'.join (commit_msg).rstrip())
|
||||
|
||||
def cb_rescan (self, model, *args):
|
||||
'''Populates view widgets with results from "git status."'''
|
||||
|
||||
# This allows us to defer notification until the
|
||||
# we finish processing data
|
||||
model.set_notify(False)
|
||||
|
||||
# Reset the staged and unstaged model lists
|
||||
# NOTE: the model's unstaged list is used to
|
||||
# hold both unstaged and untracked files.
|
||||
model.staged = []
|
||||
model.unstaged = []
|
||||
model.untracked = []
|
||||
|
||||
# Read git status items
|
||||
( staged_items,
|
||||
unstaged_items,
|
||||
untracked_items ) = ugitcmds.git_status()
|
||||
|
||||
# Gather items to be committed
|
||||
for staged in staged_items:
|
||||
if staged not in model.get_staged():
|
||||
model.add_staged (staged)
|
||||
|
||||
# Gather unindexed items
|
||||
for unstaged in unstaged_items:
|
||||
if unstaged not in model.get_unstaged():
|
||||
model.add_unstaged (unstaged)
|
||||
|
||||
# Gather untracked items
|
||||
for untracked in untracked_items:
|
||||
if untracked not in model.get_untracked():
|
||||
model.add_untracked (untracked)
|
||||
|
||||
# Re-enable notifications and emit changes
|
||||
model.set_notify(True)
|
||||
model.notify_observers ('staged', 'unstaged')
|
||||
|
||||
def cb_show_diffstat (self, model, *args):
|
||||
'''Show the diffstat from the latest commit.'''
|
||||
output = ugitutils.ansi_to_html (ugitcmds.git_diff_stat())
|
||||
doc = ugitutils.html_document (output)
|
||||
self.__show_command_output (doc, rescan=False)
|
||||
|
||||
def cb_signoff (self, model, *args):
|
||||
'''Adds a standard Signed-off by: tag to the end
|
||||
of the current commit message.'''
|
||||
|
||||
msg = model.get_commitmsg()
|
||||
signoff = 'Signed-off by: %s <%s>' % (
|
||||
model.get_name(), model.get_email() )
|
||||
|
||||
if signoff not in msg:
|
||||
model.set_commitmsg( '%s\n\n%s' % ( msg, signoff ) )
|
||||
|
||||
def cb_stage_changed (self, model, *args):
|
||||
'''Stage all changed files for commit.'''
|
||||
output = ugitcmds.git_add (model.get_unstaged())
|
||||
self.__show_command_output (output, model)
|
||||
|
||||
def cb_stage_selected (self, model, *args):
|
||||
'''Use "git add" to add items to the git index.
|
||||
This is a thin wrapper around __apply_to_list.'''
|
||||
command = ugitcmds.git_add_or_remove
|
||||
widget = self.view.unstagedList
|
||||
items = model.get_unstaged() + model.get_untracked()
|
||||
self.__apply_to_list (command, model, widget, items)
|
||||
|
||||
def cb_stage_untracked (self, model, *args):
|
||||
'''Stage all untracked files for commit.'''
|
||||
output = ugitcmds.git_add (model.get_untracked())
|
||||
self.__show_command_output (output, model)
|
||||
|
||||
def cb_unstage_all (self, model, *args):
|
||||
'''Use "git reset" to remove all items from the git index.'''
|
||||
output = ugitcmds.git_reset (model.get_staged())
|
||||
self.__show_command_output (output, model)
|
||||
|
||||
def cb_unstage_selected (self, model, *args):
|
||||
'''Use "git reset" to remove items from the git index.
|
||||
This is a thin wrapper around __apply_to_list.'''
|
||||
|
||||
command = ugitcmds.git_reset
|
||||
widget = self.view.stagedList
|
||||
items = model.get_staged()
|
||||
self.__apply_to_list (command, model, widget, items)
|
||||
|
||||
def cb_viz_all (self, model):
|
||||
'''Visualizes the entire git history using gitk.'''
|
||||
os.system ('gitk --all &')
|
||||
|
||||
def cb_viz_current (self, model):
|
||||
'''Visualizes the current branch's history using gitk.'''
|
||||
branch = ugitcmds.git_current_branch()
|
||||
os.system ('gitk %s &' % ugitutils.shell_quote (branch))
|
||||
|
||||
#####################################################################
|
||||
# PRIVATE HELPER METHODS
|
||||
#####################################################################
|
||||
|
||||
def __apply_to_list (self, command, model, widget, items):
|
||||
'''This is a helper method that retrieves the current
|
||||
selection list, applies a command to that list,
|
||||
displays a dialog showing the output of that command,
|
||||
and calls cb_rescan to pickup changes.'''
|
||||
apply_items = self.__get_selection_from_view (widget, items)
|
||||
output = command (apply_items)
|
||||
self.__show_command_output (output, model)
|
||||
|
||||
def __file_to_widget_item (self, filename, staged, untracked=False):
|
||||
'''Given a filename, return a QListWidgetItem suitable
|
||||
for adding to a QListWidget. "staged" controls whether
|
||||
to use icons for the staged or unstaged list widget.'''
|
||||
|
||||
if staged:
|
||||
icon_file = ugitutils.get_staged_icon (filename)
|
||||
elif untracked:
|
||||
icon_file = ugitutils.get_untracked_icon()
|
||||
else:
|
||||
icon_file = ugitutils.get_icon (filename)
|
||||
|
||||
icon = QIcon (QPixmap (icon_file))
|
||||
item = QListWidgetItem()
|
||||
item.setText (filename)
|
||||
item.setIcon (icon)
|
||||
return item
|
||||
|
||||
def __get_selected_row (self, list_widget):
|
||||
row = list_widget.currentRow()
|
||||
item = list_widget.item (row)
|
||||
selected = item.isSelected()
|
||||
return (row, selected)
|
||||
|
||||
def __get_selection_from_view (self, list_widget, items):
|
||||
'''Returns an array of model items that correspond to
|
||||
the selected QListWidget indices.'''
|
||||
selected = []
|
||||
for idx in range (list_widget.count()):
|
||||
item = list_widget.item (idx)
|
||||
if item.isSelected():
|
||||
selected.append (items[idx])
|
||||
return selected
|
||||
|
||||
def __select_commits (self, revs, summaries):
|
||||
'''Use the GitCommitBrowser to select commits from a list.'''
|
||||
if not summaries:
|
||||
msg = 'ERROR: No commits exist in this branch.'''
|
||||
self.__show_command_output (output=msg)
|
||||
return ([],[])
|
||||
|
||||
browser = GitCommitBrowser (self.view)
|
||||
QObject.connect ( browser.commitList,
|
||||
SIGNAL ('itemSelectionChanged()'),
|
||||
lambda: self.cb_commit_sha1_selected(
|
||||
browser, revs) )
|
||||
|
||||
for summary in summaries:
|
||||
browser.commitList.addItem (summary)
|
||||
|
||||
browser.show()
|
||||
result = browser.exec_()
|
||||
if result != QDialog.Accepted:
|
||||
return ([],[])
|
||||
|
||||
list_widget = browser.commitList
|
||||
selection = self.__get_selection_from_view (list_widget, revs)
|
||||
if not selection: return ([],[])
|
||||
|
||||
# also return the selected index numbers
|
||||
index_nums = range (len (revs))
|
||||
idxs = self.__get_selection_from_view (list_widget, index_nums)
|
||||
|
||||
return (selection, idxs)
|
||||
|
||||
def __set_branch_ui_items (self):
|
||||
'''Sets up items that mention the current branch name.'''
|
||||
current_branch = ugitcmds.git_current_branch()
|
||||
menu_text = 'Browse ' + current_branch + ' branch'
|
||||
self.view.browseBranch.setText (menu_text)
|
||||
|
||||
status_text = 'Current branch: ' + current_branch
|
||||
self.view.statusBar().showMessage (status_text)
|
||||
|
||||
def __show_command_output (self, output, model=None, rescan=True):
|
||||
'''Shows output in a GitCommandDialog and optionally
|
||||
rescans for changes.'''
|
||||
dialog = GitCommandDialog (self.view, output=output)
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
if rescan and model: self.cb_rescan (model)
|
||||
|
||||
def __update_list_widget (self, list_widget, items,
|
||||
staged, untracked=False, append=False):
|
||||
'''A helper method to populate a QListWidget with the
|
||||
contents of modelitems.'''
|
||||
if not append:
|
||||
list_widget.clear()
|
||||
for item in items:
|
||||
qitem = self.__file_to_widget_item (item,
|
||||
staged, untracked)
|
||||
list_widget.addItem( qitem )
|
||||
@@ -1,15 +0,0 @@
|
||||
import commands
|
||||
from model import Model
|
||||
|
||||
def get_config(key):
|
||||
return commands.getoutput('git config --get "%s"' % key)
|
||||
|
||||
class GitModel(Model):
|
||||
def __init__ (self): Model.__init__(self, {
|
||||
'commitmsg': '',
|
||||
'staged': [],
|
||||
'unstaged': [],
|
||||
'untracked': [],
|
||||
'name': get_config('user.name'),
|
||||
'email': get_config('user.email'),
|
||||
})
|
||||
-429
@@ -1,429 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import re
|
||||
import commands
|
||||
|
||||
KNOWN_FILE_TYPES = {
|
||||
'ascii c': 'c.png',
|
||||
'python': 'script.png',
|
||||
'ruby': 'script.png',
|
||||
'shell': 'script.png',
|
||||
'perl': 'script.png',
|
||||
'java': 'script.png',
|
||||
'assembler': 'binary.png',
|
||||
'binary': 'binary.png',
|
||||
'byte': 'binary.png',
|
||||
'image': 'image.png',
|
||||
}
|
||||
|
||||
ICONSDIR = os.path.join (os.path.dirname (__file__), 'icons')
|
||||
|
||||
def ident_file_type (filename):
|
||||
'''Returns an icon based on the contents of filename.'''
|
||||
if os.path.exists (filename):
|
||||
quoted_filename = shell_quote (filename)
|
||||
fileinfo = commands.getoutput('file -b %s' % quoted_filename)
|
||||
for filetype, iconname in KNOWN_FILE_TYPES.iteritems():
|
||||
if filetype in fileinfo.lower():
|
||||
return iconname
|
||||
else:
|
||||
return 'removed.png'
|
||||
# Fallback for modified files of an unknown type
|
||||
return 'generic.png'
|
||||
|
||||
def get_icon (filename):
|
||||
'''Returns the full path to an icon file corresponding to
|
||||
filename's contents.'''
|
||||
icon_file = ident_file_type (filename)
|
||||
return os.path.join (ICONSDIR, icon_file)
|
||||
|
||||
def get_staged_icon (filename):
|
||||
'''Special-case method for staged items. These are only
|
||||
ever 'staged' and 'removed' items in the staged list.'''
|
||||
|
||||
if os.path.exists (filename):
|
||||
return os.path.join (ICONSDIR, 'staged.png')
|
||||
else:
|
||||
return os.path.join (ICONSDIR, 'removed.png')
|
||||
|
||||
def get_untracked_icon():
|
||||
return os.path.join (ICONSDIR, 'untracked.png')
|
||||
|
||||
def shell_quote (*inputs):
|
||||
'''Quote strings so that they can be suitably martialled
|
||||
off to the shell. This method supports POSIX sh syntax.
|
||||
This is crucial to properly handle command line arguments
|
||||
with spaces, quotes, double-quotes, etc.'''
|
||||
|
||||
regex = re.compile ('[^\w!%+,\-./:@^]')
|
||||
quote_regex = re.compile ("((?:'\\''){2,})")
|
||||
|
||||
ret = []
|
||||
for input in inputs:
|
||||
if not input:
|
||||
continue
|
||||
|
||||
if '\x00' in input:
|
||||
raise AssertionError, ('No way to quote strings '
|
||||
'containing null (\\000) bytes')
|
||||
|
||||
# = does need quoting else in command position it's a
|
||||
# program-local environment setting
|
||||
match = regex.search (input)
|
||||
if match:
|
||||
# ' -> '\''
|
||||
input = input.replace ("'", "'\\''")
|
||||
|
||||
# make multiple ' in a row look simpler
|
||||
# '\'''\'''\'' -> '"'''"'
|
||||
quote_match = quote_regex.match (input)
|
||||
if quote_match:
|
||||
quotes = match.group (1)
|
||||
input.replace (quotes,
|
||||
("'" * (len(quotes)/4)) + "\"'")
|
||||
|
||||
input = "'%s'" % input
|
||||
if input.startswith ("''"):
|
||||
input = input.lstrip ("''")
|
||||
|
||||
if input.endswith ("''"):
|
||||
input = input.rstrip ("''")
|
||||
ret.append (input)
|
||||
return ' '.join (ret)
|
||||
|
||||
ANSI_BACKGROUND_COLOR = 41
|
||||
ANSI_TABLE = {
|
||||
'1': 'grey',
|
||||
'30': 'black',
|
||||
'31': 'red',
|
||||
'32': 'green',
|
||||
'33': 'yellow',
|
||||
'34': 'blue',
|
||||
'35': 'magenta',
|
||||
'36': 'cyan',
|
||||
'37': 'white',
|
||||
}
|
||||
|
||||
|
||||
def html_document (html_fragment):
|
||||
return '<body>%s</body>' % html_fragment
|
||||
|
||||
def ansi_to_html (ansi):
|
||||
'''Converts a block of text into an equivalent html fragment.'''
|
||||
|
||||
html = []
|
||||
regex = re.compile ('(.*?)\x1b\[(\d*)m')
|
||||
|
||||
for line in ansi.split ('\n'):
|
||||
|
||||
linecopy = str (html_encode(line))
|
||||
match = regex.match (linecopy)
|
||||
tagged = False
|
||||
|
||||
while match:
|
||||
start, end = match.span()
|
||||
|
||||
prefix = match.group (1)
|
||||
middle = match.group (2)
|
||||
postfix = linecopy[end:]
|
||||
|
||||
if middle in ANSI_TABLE:
|
||||
color = ANSI_TABLE[middle]
|
||||
middle = '<span style="color: %s">' % color
|
||||
tagged = True
|
||||
elif middle == str (ANSI_BACKGROUND_COLOR):
|
||||
middle = '<span style="background-color:red">'
|
||||
tagged = True
|
||||
else:
|
||||
if tagged:
|
||||
middle = '</span>'
|
||||
else:
|
||||
middle = ''
|
||||
|
||||
linecopy = str (prefix + middle + postfix)
|
||||
match = regex.match (linecopy)
|
||||
|
||||
html.append (linecopy)
|
||||
|
||||
return '<br />\n'.join (html)
|
||||
|
||||
def html_header (header):
|
||||
return '''
|
||||
<p style="color: black;
|
||||
background-color: yellow">
|
||||
%s
|
||||
</p>''' % header
|
||||
|
||||
def html_encode (ascii):
|
||||
html = []
|
||||
ANSI = [ '\x1b', '[' ]
|
||||
for char in ascii:
|
||||
if char == '\t':
|
||||
html.append ( ' ' * 8 )
|
||||
continue
|
||||
if char not in ANSI and not char.isalnum():
|
||||
html.append (ENTITIES[char])
|
||||
else:
|
||||
html.append (char)
|
||||
return ''.join (html)
|
||||
|
||||
# Keep this at the bottom of the file since it's generated output.
|
||||
# Generated by html2py.pl from HTML::Entities on Wed Dec 5 16:28:55 PST 2007
|
||||
ENTITIES = {
|
||||
chr(0) : '�',
|
||||
chr(1) : '',
|
||||
chr(2) : '',
|
||||
chr(3) : '',
|
||||
chr(4) : '',
|
||||
chr(5) : '',
|
||||
chr(6) : '',
|
||||
chr(7) : '',
|
||||
chr(8) : '',
|
||||
chr(9) : '	',
|
||||
chr(10) : ' ',
|
||||
chr(11) : '',
|
||||
chr(12) : '',
|
||||
chr(13) : ' ',
|
||||
chr(14) : '',
|
||||
chr(15) : '',
|
||||
chr(16) : '',
|
||||
chr(17) : '',
|
||||
chr(18) : '',
|
||||
chr(19) : '',
|
||||
chr(20) : '',
|
||||
chr(21) : '',
|
||||
chr(22) : '',
|
||||
chr(23) : '',
|
||||
chr(24) : '',
|
||||
chr(25) : '',
|
||||
chr(26) : '',
|
||||
chr(27) : '',
|
||||
chr(28) : '',
|
||||
chr(29) : '',
|
||||
chr(30) : '',
|
||||
chr(31) : '',
|
||||
chr(32) : ' ',
|
||||
chr(33) : '!',
|
||||
chr(34) : '"',
|
||||
chr(35) : '#',
|
||||
chr(36) : '$',
|
||||
chr(37) : '%',
|
||||
chr(38) : '&',
|
||||
chr(39) : ''',
|
||||
chr(40) : '(',
|
||||
chr(41) : ')',
|
||||
chr(42) : '*',
|
||||
chr(43) : '+',
|
||||
chr(44) : ',',
|
||||
chr(45) : '-',
|
||||
chr(46) : '.',
|
||||
chr(47) : '/',
|
||||
chr(48) : '0',
|
||||
chr(49) : '1',
|
||||
chr(50) : '2',
|
||||
chr(51) : '3',
|
||||
chr(52) : '4',
|
||||
chr(53) : '5',
|
||||
chr(54) : '6',
|
||||
chr(55) : '7',
|
||||
chr(56) : '8',
|
||||
chr(57) : '9',
|
||||
chr(58) : ':',
|
||||
chr(59) : ';',
|
||||
chr(60) : '<',
|
||||
chr(61) : '=',
|
||||
chr(62) : '>',
|
||||
chr(63) : '?',
|
||||
chr(64) : '@',
|
||||
chr(65) : 'A',
|
||||
chr(66) : 'B',
|
||||
chr(67) : 'C',
|
||||
chr(68) : 'D',
|
||||
chr(69) : 'E',
|
||||
chr(70) : 'F',
|
||||
chr(71) : 'G',
|
||||
chr(72) : 'H',
|
||||
chr(73) : 'I',
|
||||
chr(74) : 'J',
|
||||
chr(75) : 'K',
|
||||
chr(76) : 'L',
|
||||
chr(77) : 'M',
|
||||
chr(78) : 'N',
|
||||
chr(79) : 'O',
|
||||
chr(80) : 'P',
|
||||
chr(81) : 'Q',
|
||||
chr(82) : 'R',
|
||||
chr(83) : 'S',
|
||||
chr(84) : 'T',
|
||||
chr(85) : 'U',
|
||||
chr(86) : 'V',
|
||||
chr(87) : 'W',
|
||||
chr(88) : 'X',
|
||||
chr(89) : 'Y',
|
||||
chr(90) : 'Z',
|
||||
chr(91) : '[',
|
||||
chr(92) : '\',
|
||||
chr(93) : ']',
|
||||
chr(94) : '^',
|
||||
chr(95) : '_',
|
||||
chr(96) : '`',
|
||||
chr(97) : 'a',
|
||||
chr(98) : 'b',
|
||||
chr(99) : 'c',
|
||||
chr(100) : 'd',
|
||||
chr(101) : 'e',
|
||||
chr(102) : 'f',
|
||||
chr(103) : 'g',
|
||||
chr(104) : 'h',
|
||||
chr(105) : 'i',
|
||||
chr(106) : 'j',
|
||||
chr(107) : 'k',
|
||||
chr(108) : 'l',
|
||||
chr(109) : 'm',
|
||||
chr(110) : 'n',
|
||||
chr(111) : 'o',
|
||||
chr(112) : 'p',
|
||||
chr(113) : 'q',
|
||||
chr(114) : 'r',
|
||||
chr(115) : 's',
|
||||
chr(116) : 't',
|
||||
chr(117) : 'u',
|
||||
chr(118) : 'v',
|
||||
chr(119) : 'w',
|
||||
chr(120) : 'x',
|
||||
chr(121) : 'y',
|
||||
chr(122) : 'z',
|
||||
chr(123) : '{',
|
||||
chr(124) : '|',
|
||||
chr(125) : '}',
|
||||
chr(126) : '~',
|
||||
chr(127) : '',
|
||||
chr(128) : '€',
|
||||
chr(129) : '',
|
||||
chr(130) : '‚',
|
||||
chr(131) : 'ƒ',
|
||||
chr(132) : '„',
|
||||
chr(133) : '…',
|
||||
chr(134) : '†',
|
||||
chr(135) : '‡',
|
||||
chr(136) : 'ˆ',
|
||||
chr(137) : '‰',
|
||||
chr(138) : 'Š',
|
||||
chr(139) : '‹',
|
||||
chr(140) : 'Œ',
|
||||
chr(141) : '',
|
||||
chr(142) : 'Ž',
|
||||
chr(143) : '',
|
||||
chr(144) : '',
|
||||
chr(145) : '‘',
|
||||
chr(146) : '’',
|
||||
chr(147) : '“',
|
||||
chr(148) : '”',
|
||||
chr(149) : '•',
|
||||
chr(150) : '–',
|
||||
chr(151) : '—',
|
||||
chr(152) : '˜',
|
||||
chr(153) : '™',
|
||||
chr(154) : 'š',
|
||||
chr(155) : '›',
|
||||
chr(156) : 'œ',
|
||||
chr(157) : '',
|
||||
chr(158) : 'ž',
|
||||
chr(159) : 'Ÿ',
|
||||
chr(160) : ' ',
|
||||
chr(161) : '¡',
|
||||
chr(162) : '¢',
|
||||
chr(163) : '£',
|
||||
chr(164) : '¤',
|
||||
chr(165) : '¥',
|
||||
chr(166) : '¦',
|
||||
chr(167) : '§',
|
||||
chr(168) : '¨',
|
||||
chr(169) : '©',
|
||||
chr(170) : 'ª',
|
||||
chr(171) : '«',
|
||||
chr(172) : '¬',
|
||||
chr(173) : '­',
|
||||
chr(174) : '®',
|
||||
chr(175) : '¯',
|
||||
chr(176) : '°',
|
||||
chr(177) : '±',
|
||||
chr(178) : '²',
|
||||
chr(179) : '³',
|
||||
chr(180) : '´',
|
||||
chr(181) : 'µ',
|
||||
chr(182) : '¶',
|
||||
chr(183) : '·',
|
||||
chr(184) : '¸',
|
||||
chr(185) : '¹',
|
||||
chr(186) : 'º',
|
||||
chr(187) : '»',
|
||||
chr(188) : '¼',
|
||||
chr(189) : '½',
|
||||
chr(190) : '¾',
|
||||
chr(191) : '¿',
|
||||
chr(192) : 'À',
|
||||
chr(193) : 'Á',
|
||||
chr(194) : 'Â',
|
||||
chr(195) : 'Ã',
|
||||
chr(196) : 'Ä',
|
||||
chr(197) : 'Å',
|
||||
chr(198) : 'Æ',
|
||||
chr(199) : 'Ç',
|
||||
chr(200) : 'È',
|
||||
chr(201) : 'É',
|
||||
chr(202) : 'Ê',
|
||||
chr(203) : 'Ë',
|
||||
chr(204) : 'Ì',
|
||||
chr(205) : 'Í',
|
||||
chr(206) : 'Î',
|
||||
chr(207) : 'Ï',
|
||||
chr(208) : 'Ð',
|
||||
chr(209) : 'Ñ',
|
||||
chr(210) : 'Ò',
|
||||
chr(211) : 'Ó',
|
||||
chr(212) : 'Ô',
|
||||
chr(213) : 'Õ',
|
||||
chr(214) : 'Ö',
|
||||
chr(215) : '×',
|
||||
chr(216) : 'Ø',
|
||||
chr(217) : 'Ù',
|
||||
chr(218) : 'Ú',
|
||||
chr(219) : 'Û',
|
||||
chr(220) : 'Ü',
|
||||
chr(221) : 'Ý',
|
||||
chr(222) : 'Þ',
|
||||
chr(223) : 'ß',
|
||||
chr(224) : 'à',
|
||||
chr(225) : 'á',
|
||||
chr(226) : 'â',
|
||||
chr(227) : 'ã',
|
||||
chr(228) : 'ä',
|
||||
chr(229) : 'å',
|
||||
chr(230) : 'æ',
|
||||
chr(231) : 'ç',
|
||||
chr(232) : 'è',
|
||||
chr(233) : 'é',
|
||||
chr(234) : 'ê',
|
||||
chr(235) : 'ë',
|
||||
chr(236) : 'ì',
|
||||
chr(237) : 'í',
|
||||
chr(238) : 'î',
|
||||
chr(239) : 'ï',
|
||||
chr(240) : 'ð',
|
||||
chr(241) : 'ñ',
|
||||
chr(242) : 'ò',
|
||||
chr(243) : 'ó',
|
||||
chr(244) : 'ô',
|
||||
chr(245) : 'õ',
|
||||
chr(246) : 'ö',
|
||||
chr(247) : '÷',
|
||||
chr(248) : 'ø',
|
||||
chr(249) : 'ù',
|
||||
chr(250) : 'ú',
|
||||
chr(251) : 'û',
|
||||
chr(252) : 'ü',
|
||||
chr(253) : 'ý',
|
||||
chr(254) : 'þ',
|
||||
chr(255) : 'ÿ',
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import os
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
from ugitWindow import Ui_ugitWindow
|
||||
from ugitCommandDialog import Ui_ugitCommandDialog
|
||||
from ugitCommitBrowser import Ui_ugitCommitBrowser
|
||||
|
||||
class GitView (Ui_ugitWindow, QtGui.QMainWindow):
|
||||
'''The main ugit interface.'''
|
||||
def __init__ (self, parent=None, autosetup=True):
|
||||
QtGui.QMainWindow.__init__ (self, parent)
|
||||
Ui_ugitWindow.__init__ (self)
|
||||
if autosetup:
|
||||
self.setupUi (self)
|
||||
|
||||
class GitCommandDialog (Ui_ugitCommandDialog, QtGui.QDialog):
|
||||
'''A simple dialog to display command output.'''
|
||||
def __init__ (self, parent=None, output=None, autosetup=True):
|
||||
QtGui.QDialog.__init__ (self, parent)
|
||||
Ui_ugitCommandDialog.__init__ (self)
|
||||
if autosetup:
|
||||
self.setupUi (self)
|
||||
if output is not None:
|
||||
self.set_command_output (output)
|
||||
|
||||
def set_command_output (self, output):
|
||||
self.commandText.setText (output)
|
||||
|
||||
class GitCommitBrowser (Ui_ugitCommitBrowser, QtGui.QDialog):
|
||||
'''A dialog to display commits in for selection.'''
|
||||
def __init__ (self, parent=None, autosetup=True):
|
||||
QtGui.QDialog.__init__ (self, parent)
|
||||
Ui_ugitCommitBrowser.__init__ (self)
|
||||
if autosetup:
|
||||
self.setupUi (self)
|
||||
# Make the commit list slighty larger than the
|
||||
# commit message widget
|
||||
self.splitter.setSizes ([ 50, 200 ])
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
def build(bld):
|
||||
pyqt = bld.create_obj ('py')
|
||||
pyqt.inst_var = 'PYMODS_UGIT'
|
||||
pyqt.find_sources_in_dirs ('.')
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
. $(dirname $0)/common.sh
|
||||
|
||||
PREFIX=installroot
|
||||
BINDIR=$PREFIX/bin
|
||||
DOCDIR=$PREFIX/share/doc/cola
|
||||
|
||||
try_python /usr/bin/python2.5 "$PREFIX"
|
||||
|
||||
# no symlinks on win32
|
||||
rm -f $BINDIR/bin/cola
|
||||
|
||||
cp scripts/py2exe-* $BINDIR
|
||||
cp scripts/cola-win32.sh $BINDIR
|
||||
|
||||
mkdir -p $DOCDIR
|
||||
cp README $DOCDIR/README.txt
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
# This should work if used with both the default Python2.5
|
||||
# from python.org and the default PyQt4 binary package
|
||||
# from Riverbank. Adjust accordingly.
|
||||
DIR=$(dirname "$0")
|
||||
c:/Python25/python.exe "$DIR"/git-cola
|
||||
@@ -0,0 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Encoding=UTF-8
|
||||
Name=cola
|
||||
Comment=cola: a highly caffeinated git gui
|
||||
Exec=git cola
|
||||
Icon=/usr/share/cola/icons/git.png
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Development
|
||||
X-KDE-SubstituteUID=false
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/bin/sh
|
||||
|
||||
waf_backup()
|
||||
{
|
||||
if [ -e .lock-wscript ]; then
|
||||
mv .lock-wscript .lock-wscript.old
|
||||
fi
|
||||
}
|
||||
|
||||
waf_restore()
|
||||
{
|
||||
if [ -e .lock-wscript.old ]; then
|
||||
mv .lock-wscript.old .lock-wscript
|
||||
fi
|
||||
}
|
||||
|
||||
try_python()
|
||||
{
|
||||
waf_backup
|
||||
echo "+------------------------------------------------"
|
||||
echo "+ PYTHON=$1"
|
||||
echo "+------------------------------------------------"
|
||||
if [ ! -x "$1" ]; then
|
||||
echo "$1 is not executable."
|
||||
exit -1
|
||||
fi
|
||||
env PYTHON="$1" \
|
||||
./configure \
|
||||
--prefix="$2" \
|
||||
--blddir="$PWD/tmp.$$" &&
|
||||
make &&
|
||||
make install
|
||||
status=$?
|
||||
|
||||
rm -rf "$PWD/tmp.$$"
|
||||
|
||||
if test -z $status; then
|
||||
echo "exited with status code $status"
|
||||
exit $status
|
||||
fi
|
||||
|
||||
waf_restore
|
||||
}
|
||||
|
||||
cd $(dirname $0)
|
||||
cd ..
|
||||
|
||||
VERSION=$(scripts/version.sh)
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
VN=$(git describe HEAD 2>/dev/null)
|
||||
VN=$(echo "$VN" | sed -e 's/-/./g')
|
||||
LF='
|
||||
'
|
||||
case "$VN" in
|
||||
*$LF*) (exit 1) ;;
|
||||
v[0-9]*)
|
||||
test -z "$(git diff-index --name-only HEAD)" ||
|
||||
VN="$VN-dirty" ;;
|
||||
esac
|
||||
VN=$(expr "$VN" : v*'\(.*\)')
|
||||
echo "VERSION='$VN'"
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use HTML::Entities;
|
||||
|
||||
my $date = `date`;
|
||||
chomp $date;
|
||||
print "# Generated by html2py.pl from HTML::Entities on $date\n";
|
||||
print "ENTITIES = {\n";
|
||||
|
||||
for my $k (sort keys %HTML::Entities::char2entity) {
|
||||
|
||||
my $int = ord($k);
|
||||
if ( $int > 255 ) {
|
||||
# These are either:
|
||||
# - outside of ord()'s 0-255 range
|
||||
# - used in ANSI color sequences handled by
|
||||
# ugitutils` ansi_to_html.
|
||||
next;
|
||||
}
|
||||
my $v = $HTML::Entities::char2entity{$k};
|
||||
|
||||
print "\t";
|
||||
print "chr($int)";
|
||||
print "\t:\t";
|
||||
print "'$v',\n";
|
||||
}
|
||||
print "}\n";
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
. $(dirname $0)/common.sh
|
||||
waf_backup
|
||||
dpkg-buildpackage -A
|
||||
waf_restore
|
||||
|
||||
rm -rf debian/bld
|
||||
rm -rf debian/cola
|
||||
rm ../cola_*.changes
|
||||
mv ../cola_*.deb .
|
||||
|
||||
alien --to-rpm cola_*.deb
|
||||
|
||||
if [ -d $HOME/htdocs/cola/releases ];
|
||||
then
|
||||
mv cola_*.deb cola-*.rpm $HOME/htdocs/cola/releases
|
||||
fi
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
. $(dirname $0)/common.sh
|
||||
|
||||
VERSION=$(scripts/version.sh)
|
||||
BASENAME=cola-win32-$VERSION
|
||||
FILE=$BASENAME.tar.gz
|
||||
DIR=installroot
|
||||
|
||||
if [ -d $DIR ]; then
|
||||
echo "ERROR: '$DIR' already exists."
|
||||
exit -1
|
||||
fi
|
||||
|
||||
scripts/build-win32.sh || exit -1
|
||||
|
||||
if [ -e $BASENAME ]; then
|
||||
echo "error: $BASENAME exists"
|
||||
exit -1
|
||||
fi
|
||||
rsync -avr $DIR/ $BASENAME/
|
||||
tar czf $FILE $BASENAME/
|
||||
rm -rf $DIR $BASENAME
|
||||
if [ -e $HOME/htdocs/cola/releases/win32 ]; then
|
||||
mv -v $FILE $HOME/htdocs/cola/releases/win32
|
||||
fi
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
. $(dirname $0)/common.sh
|
||||
BASENAME=cola-$VERSION
|
||||
FILE=$BASENAME.tar.gz
|
||||
DIR=installroot
|
||||
if [ -d $DIR ]; then
|
||||
echo "ERROR: '$DIR' already exists"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
try_python "$(which python)" "$DIR"
|
||||
|
||||
rsync -avr $DIR/ $BASENAME/ &&
|
||||
tar czf $FILE $BASENAME/ &&
|
||||
rm -rf $DIR $BASENAME
|
||||
|
||||
if [ -d $HOME/htdocs/cola/releases ]; then
|
||||
mv -v $FILE $HOME/htdocs/cola/releases
|
||||
fi
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
@python py2exe-setup.py py2exe
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
from distutils.core import setup
|
||||
import py2exe
|
||||
|
||||
setup(
|
||||
windows = [
|
||||
{
|
||||
"script" : "git-cola",
|
||||
},
|
||||
],
|
||||
options = {
|
||||
"py2exe" : {
|
||||
"includes" : ["PyQt4._qt", "pprint", "sip"],
|
||||
}
|
||||
}
|
||||
)
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
import py_compile
|
||||
for filename in sys.argv[1:]:
|
||||
py_compile.compile(filename, doraise=True)
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
VERSION=$(git describe HEAD 2>/dev/null)
|
||||
VERSION=$(echo "$VERSION" | sed -e 's/^v//')
|
||||
VERSION=$(echo "$VERSION" | sed -e 's/-/./g')
|
||||
VERSION=$(echo "$VERSION" | perl -p -e 's/(\d+\.\d+\.\d+)(\.[^.]+)$/\1/')
|
||||
echo $VERSION
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import Common
|
||||
import Params
|
||||
from os.path import exists
|
||||
from os.path import join
|
||||
|
||||
def gen_install(path):
|
||||
def install():
|
||||
Common.install_files('COLA_MODULES', '', path)
|
||||
return install
|
||||
|
||||
def build(bld):
|
||||
vgen = bld.create_obj('command-output')
|
||||
vgen.command = 'gitversion.sh'
|
||||
vgen.stdin = 'gitversion.sh'
|
||||
vgen.stdout = 'version.py'
|
||||
vgen.install = gen_install('../scripts/version.py')
|
||||
Common.install_files('COLA_APPS', '', 'cola.desktop')
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd $(dirname $0)
|
||||
|
||||
ERROR=''
|
||||
COLA=$(which cola)
|
||||
test -z "$COLA" && ERROR=1
|
||||
|
||||
if [ $ERROR ]; then
|
||||
echo "cola must be in your path in order to call run_tests directly."
|
||||
exit -1
|
||||
fi
|
||||
BINDIR=$(dirname $COLA)
|
||||
PREFIX=$(dirname $BINDIR)
|
||||
PYTHONPATH="$PREFIX"/share:"$PREFIX"/share/cola:"$PYTHONPATH"
|
||||
export PYTHONPATH
|
||||
|
||||
(
|
||||
for unittest in t[0-9]*.py; do
|
||||
echo
|
||||
echo -n "$unittest "
|
||||
./$unittest
|
||||
if [ $? -ne 0 ] && [ -z "$RUN_ALL" ]; then
|
||||
echo "$unittest failed with exit code $?"
|
||||
exit -1
|
||||
fi
|
||||
done
|
||||
|
||||
) 2>&1 | perl -e '
|
||||
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
my ($start_sec, $start_ms) = gettimeofday();
|
||||
|
||||
my $count = 0;
|
||||
my @fail = ();
|
||||
while(<>) {
|
||||
print;
|
||||
if ( /(\d+) tests/ ) {
|
||||
$count += $1;
|
||||
}
|
||||
elsif ( /(.*) failed/ ) {
|
||||
push @fail, $1;
|
||||
}
|
||||
}
|
||||
|
||||
my ($end_sec, $end_ms) = gettimeofday();
|
||||
|
||||
my $min = 0;
|
||||
my $sec = $end_sec - $start_sec;
|
||||
my $secfrac = ( $end_ms - $start_ms ) * 100;
|
||||
|
||||
while ( $secfrac < 0.0 ) {
|
||||
$secfrac += 1000 * 100;
|
||||
}
|
||||
|
||||
$secfrac = substr("$secfrac", 0, 2);
|
||||
|
||||
while ( $sec > 60 ) {
|
||||
$sec -= 60;
|
||||
$min++;
|
||||
}
|
||||
|
||||
if ( @fail ) {
|
||||
print "\n";
|
||||
print "The following test FAILED:\n";
|
||||
print "-"x70 ."\n\t";
|
||||
print join("\n\t", @fail);
|
||||
print "\n";
|
||||
}
|
||||
|
||||
print "\n";
|
||||
print `../scripts/gitversion.sh`;
|
||||
print "-"x70 ."\n";
|
||||
print "$count tests total\n";
|
||||
print "$min min $sec.$secfrac sec total runtime\n\n";
|
||||
'
|
||||
exit 0
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
import unittest
|
||||
import os
|
||||
import imp
|
||||
|
||||
from testutils import *
|
||||
from testmodel import *
|
||||
|
||||
class ImportTest(TestCase):
|
||||
pass
|
||||
|
||||
def setup_tests():
|
||||
for module in """
|
||||
cola.model
|
||||
cola.models
|
||||
cola.qobserver
|
||||
cola.controllers
|
||||
cola.utils
|
||||
cola.qtutils
|
||||
""".split():
|
||||
def import_test(self):
|
||||
modinfo = None
|
||||
for idx, path in enumerate(module.split('.')):
|
||||
if idx == 0:
|
||||
modinfo = imp.find_module(path)
|
||||
mod = imp.load_module(module, *modinfo)
|
||||
else:
|
||||
modinfo = imp.find_module(path, modinfo[0])
|
||||
mod = imp.load_module(path, *modinfo)
|
||||
self.failUnless( mod )
|
||||
setattr(ImportTest, "test" + module.title(), import_test)
|
||||
|
||||
setup_tests()
|
||||
unittest.main()
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import testutils
|
||||
from testmodel import TestModel
|
||||
|
||||
from cola import git
|
||||
|
||||
class GitCommandTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.git = git.Git()
|
||||
|
||||
def testGitVersion(self):
|
||||
version = self.git.version()
|
||||
self.failUnless( version.startswith('git version') )
|
||||
|
||||
def testGitTag(self):
|
||||
tags = self.git.tag().splitlines()
|
||||
self.failUnless( 'v0.0' in tags )
|
||||
|
||||
def testGitShow(self):
|
||||
id = '1b9742bda5d26a4f250fa64657f66ed20624a084'
|
||||
contents = self.git.show(id).splitlines()
|
||||
self.failUnless( contents[0] == '/build' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python
|
||||
import unittest
|
||||
from testmodel import TestModel
|
||||
|
||||
class ModelTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.model = TestModel()
|
||||
def tearDown(self):
|
||||
del self.model
|
||||
|
||||
def testCreate(self):
|
||||
self.model.create(foo='bar')
|
||||
self.failUnless(self.model.get_foo()=='bar')
|
||||
|
||||
def testRegularAttributes(self):
|
||||
"""Test accessing attribute via model.attribute."""
|
||||
self.failUnless( self.model.attribute == 'value' )
|
||||
|
||||
def testMixedcaseAttributes(self):
|
||||
"""Test accessing attribute via model.Attribute."""
|
||||
self.failUnless( self.model.AtTrIbUte == 'value' )
|
||||
|
||||
def testExplicitAttr(self):
|
||||
"""Test accessing an explicit attribute. Just in case we f** up getattr"""
|
||||
self.failUnless( self.model.hello == 'world' )
|
||||
|
||||
def testRealMethod(self):
|
||||
"""Test calling a concrete model method."""
|
||||
self.failUnless( self.model.testMethod() == 'test' )
|
||||
|
||||
def testGetter(self):
|
||||
"""Test calling using the get* method."""
|
||||
self.failUnless( self.model.getAttribute() == 'value' )
|
||||
|
||||
def testSetter(self):
|
||||
"""Test using the set* method."""
|
||||
self.model.set_param('newAttribute','baz')
|
||||
self.failUnless( self.model.newattribute == 'baz' )
|
||||
self.failUnless( self.model.getNewAttribute() == 'baz' )
|
||||
|
||||
def testAddArray(self):
|
||||
"""Test using the add* array method."""
|
||||
self.model.array = []
|
||||
self.model.addArray('bar')
|
||||
self.failUnless( self.model.array[0] == 'bar' )
|
||||
|
||||
self.model.addArray('baz','quux')
|
||||
self.failUnless( self.model.array[1] == 'baz' )
|
||||
self.failUnless( self.model.array[2] == 'quux' )
|
||||
|
||||
def testAppendArray(self):
|
||||
"""Test using the append* array method."""
|
||||
self.model.array = []
|
||||
self.model.appendArray('bar')
|
||||
self.failUnless( self.model.array[0] == 'bar' )
|
||||
|
||||
self.model.appendArray('baz','quux')
|
||||
self.failUnless( self.model.array[1] == 'baz' )
|
||||
self.failUnless( self.model.array[2] == 'quux' )
|
||||
|
||||
def testDict(self):
|
||||
"""Test setting dictionary/dictionary values."""
|
||||
self.model.dict = { 'hello': 'world' }
|
||||
self.failUnless( self.model.dict['hello'] == 'world' )
|
||||
self.failUnless( self.model.getDict()['hello'] == 'world' )
|
||||
|
||||
def testFromDict(self):
|
||||
"""Test reconstituting a model from a dictionary."""
|
||||
self.model.from_dict({
|
||||
'test_dict': { 'hello':'world' },
|
||||
'test_list': [ 'foo', 'bar' ],
|
||||
'test_str': 'foo',
|
||||
})
|
||||
self.failUnless( self.model.get_test_dict()['hello'] == 'world' )
|
||||
self.failUnless( self.model.get_test_list()[1] == 'bar' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import testutils
|
||||
from testutils import pipe
|
||||
|
||||
from cola import models
|
||||
|
||||
class GitOpsTest(testutils.TestCase):
|
||||
|
||||
def testCommit(self):
|
||||
self.shell("""
|
||||
echo A > A
|
||||
echo B > B
|
||||
git init 2>&1 > /dev/null
|
||||
git add A B
|
||||
""")
|
||||
|
||||
model = models.Model()
|
||||
model.commit(m="commit test")
|
||||
log = pipe("git log --pretty=oneline | wc -l")
|
||||
|
||||
self.failUnless( '1' == log )
|
||||
|
||||
def testConfig(self):
|
||||
self.shell("""
|
||||
git init 2>&1 >/dev/null
|
||||
git config section.key value
|
||||
""")
|
||||
model = models.Model()
|
||||
value = model.config('section.key', get=True)
|
||||
|
||||
self.failUnless( value == 'value' )
|
||||
|
||||
# Test config_set
|
||||
model.config_set('section.bool', True)
|
||||
value = model.config('section.bool', get=True)
|
||||
|
||||
self.failUnless( value == 'true' )
|
||||
model.config_set('section.bool', False)
|
||||
|
||||
# Test config_dict
|
||||
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 = model.config_dict(local=False)
|
||||
|
||||
self.failUnless( type(global_dict) is dict )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import testutils
|
||||
|
||||
from testmodel import InnerModel
|
||||
from testmodel import NestedModel
|
||||
|
||||
from cola.model import Model
|
||||
|
||||
class SaveRestoreTest(testutils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
testutils.TestCase.setUp(self)
|
||||
self.nested = NestedModel()
|
||||
path = os.path.join(self.testDir(), 'test.data')
|
||||
self.nested.save(path)
|
||||
# reconstitute
|
||||
self.clone = Model.instance(path)
|
||||
|
||||
def testCloneToClass(self):
|
||||
self.failUnless( str(NestedModel) ==
|
||||
str(self.clone.__class__) )
|
||||
|
||||
def testClonedInnerToClass(self):
|
||||
self.failUnless( str(InnerModel) ==
|
||||
str(self.clone.inner.__class__) )
|
||||
|
||||
def testClonedListAndParam(self):
|
||||
self.failUnless( str(self.clone.inner.__class__) ==
|
||||
str(self.clone.innerList[0].__class__) )
|
||||
|
||||
def testList(self):
|
||||
self.failUnless( str(InnerModel) ==
|
||||
str(self.clone.innerList[1][0].__class__) )
|
||||
|
||||
def testListInList(self):
|
||||
self.failUnless( str(InnerModel) ==
|
||||
str(self.clone.innerList[2][0][0].__class__))
|
||||
|
||||
def testListInListInList(self):
|
||||
self.failUnless( str(InnerModel) ==
|
||||
str(self.clone.innerList[3][0][0][0].__class__))
|
||||
|
||||
def testDictInList(self):
|
||||
self.failUnless( str(dict) ==
|
||||
str(self.clone.innerList[4].__class__))
|
||||
|
||||
def testObjectInDictInList(self):
|
||||
self.failUnless( str(InnerModel) ==
|
||||
str(self.clone.innerList[-1]["foo"].__class__))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
from cola.model import Model
|
||||
|
||||
class TestModel(Model):
|
||||
def init(self):
|
||||
duck = Model().create(sound='quack',name='ducky')
|
||||
goose = Model().create(sound='cluck',name='goose')
|
||||
|
||||
self.create(attribute = 'value',
|
||||
mylist=[duck,duck,goose])
|
||||
self.hello = 'world'
|
||||
self.set_mylist([duck,duck,goose, 'meow', 'caboose',42])
|
||||
|
||||
def testMethod(self):
|
||||
return 'test'
|
||||
|
||||
class InnerModel(Model):
|
||||
def init(self):
|
||||
self.create(foo = 'bar')
|
||||
|
||||
class NestedModel(Model):
|
||||
def init(self):
|
||||
self.create(inner = InnerModel(),
|
||||
innerList = [],
|
||||
normaList = [ 1,2,3, [4,5, [6,7,8], 9]])
|
||||
self.innerList.append(InnerModel())
|
||||
self.innerList.append([InnerModel()])
|
||||
self.innerList.append([[InnerModel()]])
|
||||
self.innerList.append([[[InnerModel(),InnerModel()]]])
|
||||
self.innerList.append({"foo": InnerModel()})
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import unittest
|
||||
from os.path import join
|
||||
from os.path import dirname
|
||||
from os.path import basename
|
||||
|
||||
TEST_SCRIPT_DIR = dirname(__file__)
|
||||
ROOT_TMP_DIR = join( dirname(TEST_SCRIPT_DIR), 'tmp' )
|
||||
TEST_TMP_DIR = join( ROOT_TMP_DIR, basename(sys.argv[0]) )
|
||||
|
||||
DEBUG_MODE = os.getenv("DEBUG",'')
|
||||
|
||||
def setup_dir(dir):
|
||||
newdir = dir
|
||||
parentdir = dirname(newdir)
|
||||
if not os.path.isdir(parentdir):
|
||||
os.mkdir(parentdir)
|
||||
if not os.path.isdir(newdir):
|
||||
os.mkdir(newdir)
|
||||
|
||||
LAST_IDX = -1
|
||||
|
||||
def test_dir():
|
||||
global LAST_IDX
|
||||
return '%s-%d.%04d' % (TEST_TMP_DIR, os.getpid(), LAST_IDX)
|
||||
|
||||
def create_test_dir():
|
||||
global LAST_IDX
|
||||
LAST_IDX += 1
|
||||
newdir = test_dir()
|
||||
setup_dir(newdir)
|
||||
os.chdir(newdir)
|
||||
return newdir
|
||||
|
||||
def remove_dir(dir):
|
||||
if not DEBUG_MODE:
|
||||
os.chdir(ROOT_TMP_DIR)
|
||||
shutil.rmtree(dir)
|
||||
|
||||
def remove_test_dir():
|
||||
global LAST_IDX
|
||||
testdir = test_dir()
|
||||
remove_dir(testdir)
|
||||
LAST_IDX -= 1
|
||||
|
||||
def shell(cmd):
|
||||
result = os.system(cmd)
|
||||
return result
|
||||
|
||||
def pipe(cmd):
|
||||
p = os.popen(cmd)
|
||||
out = p.read().strip()
|
||||
p.close()
|
||||
return out
|
||||
|
||||
# All tests that operate on temporary data
|
||||
# derive from testutils.TestCase
|
||||
class TestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_test_dir()
|
||||
def tearDown(self):
|
||||
remove_test_dir()
|
||||
def shell(self, cmd):
|
||||
result = shell(cmd)
|
||||
self.failIf(result != 0)
|
||||
def testDir(self):
|
||||
return test_dir()
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<ui version="4.0" >
|
||||
<class>bookmark</class>
|
||||
<widget class="QDialog" name="bookmark" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>531</width>
|
||||
<height>513</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Bookmarks</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QListWidget" name="bookmarks" >
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode" >
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_open" >
|
||||
<property name="text" >
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_delete" >
|
||||
<property name="text" >
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>91</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_save" >
|
||||
<property name="text" >
|
||||
<string>Save</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_cancel" >
|
||||
<property name="text" >
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>button_save</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>bookmark</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>398</x>
|
||||
<y>490</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>265</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>button_cancel</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>bookmark</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>483</x>
|
||||
<y>490</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>265</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<ui version="4.0" >
|
||||
<class>combo</class>
|
||||
<widget class="QDialog" name="combo" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>73</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Select Branch...</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QComboBox" name="items_widget" />
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="button_box" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons" >
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>items_widget</tabstop>
|
||||
<tabstop>button_box</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>combo</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>combo</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
<ui version="4.0" >
|
||||
<class>commit</class>
|
||||
<widget class="QDialog" name="commit" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>780</width>
|
||||
<height>512</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="MinimumExpanding" hsizetype="MinimumExpanding" >
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="handleWidth" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QListWidget" name="commit_list" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="MinimumExpanding" hsizetype="MinimumExpanding" >
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize" >
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font" >
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode" >
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTextEdit" name="commit_text" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="MinimumExpanding" hsizetype="MinimumExpanding" >
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize" >
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font" >
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="tabChangesFocus" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="undoRedoEnabled" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="readOnly" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Revision Expression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="revision" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="button_box" >
|
||||
<property name="standardButtons" >
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>button_box</tabstop>
|
||||
<tabstop>commit_list</tabstop>
|
||||
<tabstop>revision</tabstop>
|
||||
<tabstop>commit_text</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>commit</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>205</x>
|
||||
<y>426</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>205</x>
|
||||
<y>223</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>commit</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>205</x>
|
||||
<y>426</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>205</x>
|
||||
<y>223</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -0,0 +1,306 @@
|
||||
<ui version="4.0" >
|
||||
<class>createbranch</class>
|
||||
<widget class="QDialog" name="createbranch" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>621</width>
|
||||
<height>402</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Create Branch</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="spacing" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Branch Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="local_branch" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="branch_groupbox" >
|
||||
<property name="title" >
|
||||
<string>Starting Revision</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="spacing" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="spacing" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3" >
|
||||
<property name="text" >
|
||||
<string>Revision Expression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="revision" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QRadioButton" name="local_radio" >
|
||||
<property name="text" >
|
||||
<string>Local Branch</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="remote_radio" >
|
||||
<property name="text" >
|
||||
<string>Tracking Branch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="tag_radio" >
|
||||
<property name="text" >
|
||||
<string>Tag</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>201</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="branch_list" />
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="spacing" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="options_groupbox" >
|
||||
<property name="title" >
|
||||
<string>Options</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="spacing" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2" >
|
||||
<property name="text" >
|
||||
<string>Update Existing Branch:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="no_update_radio" >
|
||||
<property name="text" >
|
||||
<string>No</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="ffwd_only_radio" >
|
||||
<property name="text" >
|
||||
<string>Fast Forward Only</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="reset_radio" >
|
||||
<property name="text" >
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fetch_checkbox" >
|
||||
<property name="text" >
|
||||
<string>Fetch Tracking Branch</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkout_checkbox" >
|
||||
<property name="text" >
|
||||
<string>Checkout After Creation</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>328</width>
|
||||
<height>46</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="spacing" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="create_button" >
|
||||
<property name="text" >
|
||||
<string>Create Branch</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>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>cancel_button</sender>
|
||||
<signal>pressed()</signal>
|
||||
<receiver>createbranch</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>450</x>
|
||||
<y>387</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>294</x>
|
||||
<y>209</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<ui version="4.0" >
|
||||
<class>items</class>
|
||||
<widget class="QDialog" name="items" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>73</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QListWidget" name="items_widget" />
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="button_box" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons" >
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>items_widget</tabstop>
|
||||
<tabstop>button_box</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>items</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>items</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
<ui version="4.0" >
|
||||
<class>logger</class>
|
||||
<widget class="QDialog" name="logger" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>830</width>
|
||||
<height>390</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<property name="leftMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="horizontalSpacing" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="verticalSpacing" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="4" >
|
||||
<widget class="QTextEdit" name="output_text" >
|
||||
<property name="font" >
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
<pointsize>13</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="acceptDrops" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="tabChangesFocus" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="undoRedoEnabled" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="readOnly" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="acceptRichText" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags" >
|
||||
<set>Qt::NoTextInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QGroupBox" name="groupBox" >
|
||||
<property name="title" >
|
||||
<string>Search</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="spacing" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="leftMargin" >
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="topMargin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin" >
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="bottomMargin" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="search_text" />
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="next_button" >
|
||||
<property name="text" >
|
||||
<string>Next</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="prev_button" >
|
||||
<property name="text" >
|
||||
<string>Previous</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>217</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="2" >
|
||||
<widget class="QPushButton" name="clear_button" >
|
||||
<property name="text" >
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3" >
|
||||
<widget class="QPushButton" name="close_button" >
|
||||
<property name="text" >
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>close_button</tabstop>
|
||||
<tabstop>output_text</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>close_button</sender>
|
||||
<signal>released()</signal>
|
||||
<receiver>logger</receiver>
|
||||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>352</x>
|
||||
<y>277</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>199</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+1196
File diff suppressed because it is too large
Load Diff
+167
@@ -0,0 +1,167 @@
|
||||
<ui version="4.0" >
|
||||
<class>merge</class>
|
||||
<widget class="QDialog" name="merge" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>673</width>
|
||||
<height>339</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Merge</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Merge into %s</string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox" >
|
||||
<property name="title" >
|
||||
<string>Revision To Merge</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<property name="leftMargin" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="horizontalSpacing" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="verticalSpacing" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label_2" >
|
||||
<property name="text" >
|
||||
<string>Revision Expression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2" >
|
||||
<widget class="QLineEdit" name="revision" />
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QRadioButton" name="radio_local" >
|
||||
<property name="text" >
|
||||
<string>Local Branch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<widget class="QRadioButton" name="radio_remote" >
|
||||
<property name="text" >
|
||||
<string>Tracking Branch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" >
|
||||
<widget class="QRadioButton" name="radio_tag" >
|
||||
<property name="text" >
|
||||
<string>Tag</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="3" >
|
||||
<widget class="QListWidget" name="revision_list" >
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_viz" >
|
||||
<property name="text" >
|
||||
<string>Visualize</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>231</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkbox_squash" >
|
||||
<property name="text" >
|
||||
<string>Squash</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkbox_commit" >
|
||||
<property name="text" >
|
||||
<string>Commit</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_cancel" >
|
||||
<property name="text" >
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_merge" >
|
||||
<property name="text" >
|
||||
<string>Merge</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>button_cancel</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>merge</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>540</x>
|
||||
<y>316</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>336</x>
|
||||
<y>169</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
<ui version="4.0" >
|
||||
<class>options</class>
|
||||
<widget class="QDialog" name="options" >
|
||||
<property name="windowModality" >
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>654</width>
|
||||
<height>345</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Options</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3" >
|
||||
<item row="0" column="0" >
|
||||
<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_cola_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_cola_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_cola_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_cola_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_cola_savewindowsettings" >
|
||||
<property name="text" >
|
||||
<string>Remember Window Settings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4" >
|
||||
<widget class="QCheckBox" name="global_cola_saveatexit" >
|
||||
<property name="text" >
|
||||
<string>Save Window Settings At Exit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<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" >
|
||||
<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>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>cancel_button</sender>
|
||||
<signal>released()</signal>
|
||||
<receiver>options</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>563</x>
|
||||
<y>319</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>306</x>
|
||||
<y>170</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
<ui version="4.0" >
|
||||
<class>remote</class>
|
||||
<widget class="QDialog" name="remote" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>550</width>
|
||||
<height>512</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Push</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Local Branches</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="local_branch" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="local_branches" />
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2" >
|
||||
<property name="text" >
|
||||
<string>Remote</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="remotename" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="remotes" />
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3" >
|
||||
<property name="text" >
|
||||
<string>Destination Branch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="remote_branch" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="remote_branches" />
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QCheckBox" name="ffwd_only_checkbox" >
|
||||
<property name="text" >
|
||||
<string>Fast Forward Only</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="tags_checkbox" >
|
||||
<property name="text" >
|
||||
<string>Include tags</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="action_button" >
|
||||
<property name="text" >
|
||||
<string>Push</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancel_button" >
|
||||
<property name="text" >
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>cancel_button</sender>
|
||||
<signal>released()</signal>
|
||||
<receiver>remote</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>502</x>
|
||||
<y>489</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>274</x>
|
||||
<y>255</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
<ui version="4.0" >
|
||||
<class>search</class>
|
||||
<widget class="QDialog" name="search" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>550</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Search</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="0" column="0" colspan="2" >
|
||||
<widget class="QRadioButton" name="radio_range" >
|
||||
<property name="text" >
|
||||
<string>Revision Range Expression</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2" >
|
||||
<widget class="QRadioButton" name="radio_path" >
|
||||
<property name="text" >
|
||||
<string>Commits Touching Paths</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3" >
|
||||
<widget class="QRadioButton" name="radio_author" >
|
||||
<property name="text" >
|
||||
<string>Author</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="4" >
|
||||
<widget class="QRadioButton" name="radio_committer" >
|
||||
<property name="text" >
|
||||
<string>Committer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2" >
|
||||
<widget class="QRadioButton" name="radio_revision" >
|
||||
<property name="text" >
|
||||
<string>Revision ID</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" >
|
||||
<widget class="QRadioButton" name="radio_message" >
|
||||
<property name="text" >
|
||||
<string>Commit Messages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3" colspan="2" >
|
||||
<widget class="QRadioButton" name="radio_daterange" >
|
||||
<property name="text" >
|
||||
<string>Date Range (Start / End)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="maxresults_label" >
|
||||
<property name="text" >
|
||||
<string>Max Results</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QSpinBox" name="max_results" >
|
||||
<property name="minimum" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>9995</number>
|
||||
</property>
|
||||
<property name="singleStep" >
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>500</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" >
|
||||
<widget class="QRadioButton" name="radio_diff" >
|
||||
<property name="text" >
|
||||
<string>Diff Content</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3" >
|
||||
<widget class="QDateEdit" name="start_date" >
|
||||
<property name="currentSection" >
|
||||
<enum>QDateTimeEdit::DaySection</enum>
|
||||
</property>
|
||||
<property name="displayFormat" >
|
||||
<string>yyyy-MM-dd</string>
|
||||
</property>
|
||||
<property name="calendarPopup" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="4" >
|
||||
<widget class="QDateEdit" name="end_date" >
|
||||
<property name="currentSection" >
|
||||
<enum>QDateTimeEdit::DaySection</enum>
|
||||
</property>
|
||||
<property name="displayFormat" >
|
||||
<string>yyyy-MM-dd</string>
|
||||
</property>
|
||||
<property name="calendarPopup" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" >
|
||||
<widget class="QPushButton" name="button_search" >
|
||||
<property name="text" >
|
||||
<string>Search</string>
|
||||
</property>
|
||||
<property name="shortcut" >
|
||||
<string>Return</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="3" >
|
||||
<widget class="QLineEdit" name="input" />
|
||||
</item>
|
||||
<item row="3" column="4" >
|
||||
<widget class="QPushButton" name="button_browse" >
|
||||
<property name="text" >
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="handleWidth" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QListWidget" name="commit_list" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="Minimum" hsizetype="Expanding" >
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize" >
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode" >
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTextEdit" name="commit_text" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize" >
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>150</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="tabChangesFocus" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="acceptRichText" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QPushButton" name="button_export" >
|
||||
<property name="text" >
|
||||
<string>Export Patches</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<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="2" >
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>111</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="3" >
|
||||
<widget class="QPushButton" name="button_close" >
|
||||
<property name="text" >
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>button_close</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>search</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>552</x>
|
||||
<y>527</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>299</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+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>
|
||||
@@ -1,209 +0,0 @@
|
||||
<ui version="4.0" >
|
||||
<class>ugitCommandDialog</class>
|
||||
<widget class="QDialog" name="ugitCommandDialog" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Git Command Output</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Git Command Output:</string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="commandText" >
|
||||
<property name="palette" >
|
||||
<palette>
|
||||
<active>
|
||||
<colorrole role="Text" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>170</red>
|
||||
<green>170</green>
|
||||
<blue>127</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Base" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>0</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Highlight" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>0</red>
|
||||
<green>255</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="HighlightedText" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>0</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</active>
|
||||
<inactive>
|
||||
<colorrole role="Text" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>170</red>
|
||||
<green>170</green>
|
||||
<blue>127</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Base" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>0</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Highlight" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>0</red>
|
||||
<green>255</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="HighlightedText" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>0</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</inactive>
|
||||
<disabled>
|
||||
<colorrole role="Text" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>96</red>
|
||||
<green>96</green>
|
||||
<blue>96</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Base" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Highlight" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>68</red>
|
||||
<green>68</green>
|
||||
<blue>68</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="HighlightedText" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>0</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</disabled>
|
||||
</palette>
|
||||
</property>
|
||||
<property name="font" >
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
<pointsize>13</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="acceptDrops" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="readOnly" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>261</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="okButton" >
|
||||
<property name="text" >
|
||||
<string>OK</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>okButton</tabstop>
|
||||
<tabstop>commandText</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>okButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ugitCommandDialog</receiver>
|
||||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>352</x>
|
||||
<y>277</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>199</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -1,184 +0,0 @@
|
||||
<ui version="4.0" >
|
||||
<class>ugitCommitBrowser</class>
|
||||
<widget class="QDialog" name="ugitCommitBrowser" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>411</width>
|
||||
<height>448</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Git Commit Browser</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="handleWidth" >
|
||||
<number>4</number>
|
||||
</property>
|
||||
<widget class="QListWidget" name="commitList" >
|
||||
<property name="font" >
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode" >
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTextEdit" name="commitText" >
|
||||
<property name="palette" >
|
||||
<palette>
|
||||
<active>
|
||||
<colorrole role="Text" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>170</red>
|
||||
<green>170</green>
|
||||
<blue>127</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Base" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>0</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</active>
|
||||
<inactive>
|
||||
<colorrole role="Text" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>170</red>
|
||||
<green>170</green>
|
||||
<blue>127</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Base" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>0</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</inactive>
|
||||
<disabled>
|
||||
<colorrole role="Text" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>96</red>
|
||||
<green>96</green>
|
||||
<blue>96</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Base" >
|
||||
<brush brushstyle="SolidPattern" >
|
||||
<color alpha="255" >
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</disabled>
|
||||
</palette>
|
||||
</property>
|
||||
<property name="font" >
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="tabChangesFocus" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="undoRedoEnabled" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="readOnly" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Revision Expression</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="revisionLine" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||
<property name="standardButtons" >
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
<tabstop>commitList</tabstop>
|
||||
<tabstop>revisionLine</tabstop>
|
||||
<tabstop>commitText</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ugitCommitBrowser</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>205</x>
|
||||
<y>426</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>205</x>
|
||||
<y>223</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ugitCommitBrowser</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>205</x>
|
||||
<y>426</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>205</x>
|
||||
<y>223</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user