##// END OF EJS Templates
fixed license issue #149
marcink -
r1206:a671db5b beta
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (832 lines changed) Show them Hide them
@@ -1,340 +1,674 b''
1 1 GNU GENERAL PUBLIC LICENSE
2 Version 2, June 1991
2 Version 3, 29 June 2007
3 3
4 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
4 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
6 5 Everyone is permitted to copy and distribute verbatim copies
7 6 of this license document, but changing it is not allowed.
8 7
9 8 Preamble
10 9
11 The licenses for most software are designed to take away your
12 freedom to share and change it. By contrast, the GNU General Public
13 License is intended to guarantee your freedom to share and change free
14 software--to make sure the software is free for all its users. This
15 General Public License applies to most of the Free Software
16 Foundation's software and to any other program whose authors commit to
17 using it. (Some other Free Software Foundation software is covered by
18 the GNU Library General Public License instead.) You can apply it to
10 The GNU General Public License is a free, copyleft license for
11 software and other kinds of works.
12
13 The licenses for most software and other practical works are designed
14 to take away your freedom to share and change the works. By contrast,
15 the GNU General Public License is intended to guarantee your freedom to
16 share and change all versions of a program--to make sure it remains free
17 software for all its users. We, the Free Software Foundation, use the
18 GNU General Public License for most of our software; it applies also to
19 any other work released this way by its authors. You can apply it to
19 20 your programs, too.
20 21
21 22 When we speak of free software, we are referring to freedom, not
22 23 price. Our General Public Licenses are designed to make sure that you
23 24 have the freedom to distribute copies of free software (and charge for
24 this service if you wish), that you receive source code or can get it
25 if you want it, that you can change the software or use pieces of it
26 in new free programs; and that you know you can do these things.
25 them if you wish), that you receive source code or can get it if you
26 want it, that you can change the software or use pieces of it in new
27 free programs, and that you know you can do these things.
27 28
28 To protect your rights, we need to make restrictions that forbid
29 anyone to deny you these rights or to ask you to surrender the rights.
30 These restrictions translate to certain responsibilities for you if you
31 distribute copies of the software, or if you modify it.
29 To protect your rights, we need to prevent others from denying you
30 these rights or asking you to surrender the rights. Therefore, you have
31 certain responsibilities if you distribute copies of the software, or if
32 you modify it: responsibilities to respect the freedom of others.
32 33
33 34 For example, if you distribute copies of such a program, whether
34 gratis or for a fee, you must give the recipients all the rights that
35 you have. You must make sure that they, too, receive or can get the
36 source code. And you must show them these terms so they know their
37 rights.
35 gratis or for a fee, you must pass on to the recipients the same
36 freedoms that you received. You must make sure that they, too, receive
37 or can get the source code. And you must show them these terms so they
38 know their rights.
38 39
39 We protect your rights with two steps: (1) copyright the software, and
40 (2) offer you this license which gives you legal permission to copy,
41 distribute and/or modify the software.
40 Developers that use the GNU GPL protect your rights with two steps:
41 (1) assert copyright on the software, and (2) offer you this License
42 giving you legal permission to copy, distribute and/or modify it.
43
44 For the developers' and authors' protection, the GPL clearly explains
45 that there is no warranty for this free software. For both users' and
46 authors' sake, the GPL requires that modified versions be marked as
47 changed, so that their problems will not be attributed erroneously to
48 authors of previous versions.
42 49
43 Also, for each author's protection and ours, we want to make certain
44 that everyone understands that there is no warranty for this free
45 software. If the software is modified by someone else and passed on, we
46 want its recipients to know that what they have is not the original, so
47 that any problems introduced by others will not reflect on the original
48 authors' reputations.
50 Some devices are designed to deny users access to install or run
51 modified versions of the software inside them, although the manufacturer
52 can do so. This is fundamentally incompatible with the aim of
53 protecting users' freedom to change the software. The systematic
54 pattern of such abuse occurs in the area of products for individuals to
55 use, which is precisely where it is most unacceptable. Therefore, we
56 have designed this version of the GPL to prohibit the practice for those
57 products. If such problems arise substantially in other domains, we
58 stand ready to extend this provision to those domains in future versions
59 of the GPL, as needed to protect the freedom of users.
49 60
50 Finally, any free program is threatened constantly by software
51 patents. We wish to avoid the danger that redistributors of a free
52 program will individually obtain patent licenses, in effect making the
53 program proprietary. To prevent this, we have made it clear that any
54 patent must be licensed for everyone's free use or not licensed at all.
61 Finally, every program is threatened constantly by software patents.
62 States should not allow patents to restrict development and use of
63 software on general-purpose computers, but in those that do, we wish to
64 avoid the special danger that patents applied to a free program could
65 make it effectively proprietary. To prevent this, the GPL assures that
66 patents cannot be used to render the program non-free.
55 67
56 68 The precise terms and conditions for copying, distribution and
57 69 modification follow.
58 70
59 GNU GENERAL PUBLIC LICENSE
60 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
71 TERMS AND CONDITIONS
72
73 0. Definitions.
74
75 "This License" refers to version 3 of the GNU General Public License.
76
77 "Copyright" also means copyright-like laws that apply to other kinds of
78 works, such as semiconductor masks.
79
80 "The Program" refers to any copyrightable work licensed under this
81 License. Each licensee is addressed as "you". "Licensees" and
82 "recipients" may be individuals or organizations.
83
84 To "modify" a work means to copy from or adapt all or part of the work
85 in a fashion requiring copyright permission, other than the making of an
86 exact copy. The resulting work is called a "modified version" of the
87 earlier work or a work "based on" the earlier work.
88
89 A "covered work" means either the unmodified Program or a work based
90 on the Program.
61 91
62 0. This License applies to any program or other work which contains
63 a notice placed by the copyright holder saying it may be distributed
64 under the terms of this General Public License. The "Program", below,
65 refers to any such program or work, and a "work based on the Program"
66 means either the Program or any derivative work under copyright law:
67 that is to say, a work containing the Program or a portion of it,
68 either verbatim or with modifications and/or translated into another
69 language. (Hereinafter, translation is included without limitation in
70 the term "modification".) Each licensee is addressed as "you".
92 To "propagate" a work means to do anything with it that, without
93 permission, would make you directly or secondarily liable for
94 infringement under applicable copyright law, except executing it on a
95 computer or modifying a private copy. Propagation includes copying,
96 distribution (with or without modification), making available to the
97 public, and in some countries other activities as well.
98
99 To "convey" a work means any kind of propagation that enables other
100 parties to make or receive copies. Mere interaction with a user through
101 a computer network, with no transfer of a copy, is not conveying.
102
103 An interactive user interface displays "Appropriate Legal Notices"
104 to the extent that it includes a convenient and prominently visible
105 feature that (1) displays an appropriate copyright notice, and (2)
106 tells the user that there is no warranty for the work (except to the
107 extent that warranties are provided), that licensees may convey the
108 work under this License, and how to view a copy of this License. If
109 the interface presents a list of user commands or options, such as a
110 menu, a prominent item in the list meets this criterion.
111
112 1. Source Code.
71 113
72 Activities other than copying, distribution and modification are not
73 covered by this License; they are outside its scope. The act of
74 running the Program is not restricted, and the output from the Program
75 is covered only if its contents constitute a work based on the
76 Program (independent of having been made by running the Program).
77 Whether that is true depends on what the Program does.
114 The "source code" for a work means the preferred form of the work
115 for making modifications to it. "Object code" means any non-source
116 form of a work.
117
118 A "Standard Interface" means an interface that either is an official
119 standard defined by a recognized standards body, or, in the case of
120 interfaces specified for a particular programming language, one that
121 is widely used among developers working in that language.
122
123 The "System Libraries" of an executable work include anything, other
124 than the work as a whole, that (a) is included in the normal form of
125 packaging a Major Component, but which is not part of that Major
126 Component, and (b) serves only to enable use of the work with that
127 Major Component, or to implement a Standard Interface for which an
128 implementation is available to the public in source code form. A
129 "Major Component", in this context, means a major essential component
130 (kernel, window system, and so on) of the specific operating system
131 (if any) on which the executable work runs, or a compiler used to
132 produce the work, or an object code interpreter used to run it.
78 133
79 1. You may copy and distribute verbatim copies of the Program's
80 source code as you receive it, in any medium, provided that you
81 conspicuously and appropriately publish on each copy an appropriate
82 copyright notice and disclaimer of warranty; keep intact all the
83 notices that refer to this License and to the absence of any warranty;
84 and give any other recipients of the Program a copy of this License
85 along with the Program.
134 The "Corresponding Source" for a work in object code form means all
135 the source code needed to generate, install, and (for an executable
136 work) run the object code and to modify the work, including scripts to
137 control those activities. However, it does not include the work's
138 System Libraries, or general-purpose tools or generally available free
139 programs which are used unmodified in performing those activities but
140 which are not part of the work. For example, Corresponding Source
141 includes interface definition files associated with source files for
142 the work, and the source code for shared libraries and dynamically
143 linked subprograms that the work is specifically designed to require,
144 such as by intimate data communication or control flow between those
145 subprograms and other parts of the work.
86 146
87 You may charge a fee for the physical act of transferring a copy, and
88 you may at your option offer warranty protection in exchange for a fee.
147 The Corresponding Source need not include anything that users
148 can regenerate automatically from other parts of the Corresponding
149 Source.
150
151 The Corresponding Source for a work in source code form is that
152 same work.
153
154 2. Basic Permissions.
155
156 All rights granted under this License are granted for the term of
157 copyright on the Program, and are irrevocable provided the stated
158 conditions are met. This License explicitly affirms your unlimited
159 permission to run the unmodified Program. The output from running a
160 covered work is covered by this License only if the output, given its
161 content, constitutes a covered work. This License acknowledges your
162 rights of fair use or other equivalent, as provided by copyright law.
89 163
90 2. You may modify your copy or copies of the Program or any portion
91 of it, thus forming a work based on the Program, and copy and
92 distribute such modifications or work under the terms of Section 1
93 above, provided that you also meet all of these conditions:
164 You may make, run and propagate covered works that you do not
165 convey, without conditions so long as your license otherwise remains
166 in force. You may convey covered works to others for the sole purpose
167 of having them make modifications exclusively for you, or provide you
168 with facilities for running those works, provided that you comply with
169 the terms of this License in conveying all material for which you do
170 not control copyright. Those thus making or running the covered works
171 for you must do so exclusively on your behalf, under your direction
172 and control, on terms that prohibit them from making any copies of
173 your copyrighted material outside their relationship with you.
174
175 Conveying under any other circumstances is permitted solely under
176 the conditions stated below. Sublicensing is not allowed; section 10
177 makes it unnecessary.
178
179 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
181 No covered work shall be deemed part of an effective technological
182 measure under any applicable law fulfilling obligations under article
183 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 similar laws prohibiting or restricting circumvention of such
185 measures.
94 186
95 a) You must cause the modified files to carry prominent notices
96 stating that you changed the files and the date of any change.
187 When you convey a covered work, you waive any legal power to forbid
188 circumvention of technological measures to the extent such circumvention
189 is effected by exercising rights under this License with respect to
190 the covered work, and you disclaim any intention to limit operation or
191 modification of the work as a means of enforcing, against the work's
192 users, your or third parties' legal rights to forbid circumvention of
193 technological measures.
194
195 4. Conveying Verbatim Copies.
97 196
98 b) You must cause any work that you distribute or publish, that in
99 whole or in part contains or is derived from the Program or any
100 part thereof, to be licensed as a whole at no charge to all third
101 parties under the terms of this License.
197 You may convey verbatim copies of the Program's source code as you
198 receive it, in any medium, provided that you conspicuously and
199 appropriately publish on each copy an appropriate copyright notice;
200 keep intact all notices stating that this License and any
201 non-permissive terms added in accord with section 7 apply to the code;
202 keep intact all notices of the absence of any warranty; and give all
203 recipients a copy of this License along with the Program.
204
205 You may charge any price or no price for each copy that you convey,
206 and you may offer support or warranty protection for a fee.
207
208 5. Conveying Modified Source Versions.
209
210 You may convey a work based on the Program, or the modifications to
211 produce it from the Program, in the form of source code under the
212 terms of section 4, provided that you also meet all of these conditions:
213
214 a) The work must carry prominent notices stating that you modified
215 it, and giving a relevant date.
102 216
103 c) If the modified program normally reads commands interactively
104 when run, you must cause it, when started running for such
105 interactive use in the most ordinary way, to print or display an
106 announcement including an appropriate copyright notice and a
107 notice that there is no warranty (or else, saying that you provide
108 a warranty) and that users may redistribute the program under
109 these conditions, and telling the user how to view a copy of this
110 License. (Exception: if the Program itself is interactive but
111 does not normally print such an announcement, your work based on
112 the Program is not required to print an announcement.)
217 b) The work must carry prominent notices stating that it is
218 released under this License and any conditions added under section
219 7. This requirement modifies the requirement in section 4 to
220 "keep intact all notices".
221
222 c) You must license the entire work, as a whole, under this
223 License to anyone who comes into possession of a copy. This
224 License will therefore apply, along with any applicable section 7
225 additional terms, to the whole of the work, and all its parts,
226 regardless of how they are packaged. This License gives no
227 permission to license the work in any other way, but it does not
228 invalidate such permission if you have separately received it.
229
230 d) If the work has interactive user interfaces, each must display
231 Appropriate Legal Notices; however, if the Program has interactive
232 interfaces that do not display Appropriate Legal Notices, your
233 work need not make them do so.
234
235 A compilation of a covered work with other separate and independent
236 works, which are not by their nature extensions of the covered work,
237 and which are not combined with it such as to form a larger program,
238 in or on a volume of a storage or distribution medium, is called an
239 "aggregate" if the compilation and its resulting copyright are not
240 used to limit the access or legal rights of the compilation's users
241 beyond what the individual works permit. Inclusion of a covered work
242 in an aggregate does not cause this License to apply to the other
243 parts of the aggregate.
113 244
114 These requirements apply to the modified work as a whole. If
115 identifiable sections of that work are not derived from the Program,
116 and can be reasonably considered independent and separate works in
117 themselves, then this License, and its terms, do not apply to those
118 sections when you distribute them as separate works. But when you
119 distribute the same sections as part of a whole which is a work based
120 on the Program, the distribution of the whole must be on the terms of
121 this License, whose permissions for other licensees extend to the
122 entire whole, and thus to each and every part regardless of who wrote it.
245 6. Conveying Non-Source Forms.
246
247 You may convey a covered work in object code form under the terms
248 of sections 4 and 5, provided that you also convey the
249 machine-readable Corresponding Source under the terms of this License,
250 in one of these ways:
251
252 a) Convey the object code in, or embodied in, a physical product
253 (including a physical distribution medium), accompanied by the
254 Corresponding Source fixed on a durable physical medium
255 customarily used for software interchange.
256
257 b) Convey the object code in, or embodied in, a physical product
258 (including a physical distribution medium), accompanied by a
259 written offer, valid for at least three years and valid for as
260 long as you offer spare parts or customer support for that product
261 model, to give anyone who possesses the object code either (1) a
262 copy of the Corresponding Source for all the software in the
263 product that is covered by this License, on a durable physical
264 medium customarily used for software interchange, for a price no
265 more than your reasonable cost of physically performing this
266 conveying of source, or (2) access to copy the
267 Corresponding Source from a network server at no charge.
268
269 c) Convey individual copies of the object code with a copy of the
270 written offer to provide the Corresponding Source. This
271 alternative is allowed only occasionally and noncommercially, and
272 only if you received the object code with such an offer, in accord
273 with subsection 6b.
123 274
124 Thus, it is not the intent of this section to claim rights or contest
125 your rights to work written entirely by you; rather, the intent is to
126 exercise the right to control the distribution of derivative or
127 collective works based on the Program.
275 d) Convey the object code by offering access from a designated
276 place (gratis or for a charge), and offer equivalent access to the
277 Corresponding Source in the same way through the same place at no
278 further charge. You need not require recipients to copy the
279 Corresponding Source along with the object code. If the place to
280 copy the object code is a network server, the Corresponding Source
281 may be on a different server (operated by you or a third party)
282 that supports equivalent copying facilities, provided you maintain
283 clear directions next to the object code saying where to find the
284 Corresponding Source. Regardless of what server hosts the
285 Corresponding Source, you remain obligated to ensure that it is
286 available for as long as needed to satisfy these requirements.
287
288 e) Convey the object code using peer-to-peer transmission, provided
289 you inform other peers where the object code and Corresponding
290 Source of the work are being offered to the general public at no
291 charge under subsection 6d.
128 292
129 In addition, mere aggregation of another work not based on the Program
130 with the Program (or with a work based on the Program) on a volume of
131 a storage or distribution medium does not bring the other work under
132 the scope of this License.
293 A separable portion of the object code, whose source code is excluded
294 from the Corresponding Source as a System Library, need not be
295 included in conveying the object code work.
133 296
134 3. You may copy and distribute the Program (or a work based on it,
135 under Section 2) in object code or executable form under the terms of
136 Sections 1 and 2 above provided that you also do one of the following:
297 A "User Product" is either (1) a "consumer product", which means any
298 tangible personal property which is normally used for personal, family,
299 or household purposes, or (2) anything designed or sold for incorporation
300 into a dwelling. In determining whether a product is a consumer product,
301 doubtful cases shall be resolved in favor of coverage. For a particular
302 product received by a particular user, "normally used" refers to a
303 typical or common use of that class of product, regardless of the status
304 of the particular user or of the way in which the particular user
305 actually uses, or expects or is expected to use, the product. A product
306 is a consumer product regardless of whether the product has substantial
307 commercial, industrial or non-consumer uses, unless such uses represent
308 the only significant mode of use of the product.
309
310 "Installation Information" for a User Product means any methods,
311 procedures, authorization keys, or other information required to install
312 and execute modified versions of a covered work in that User Product from
313 a modified version of its Corresponding Source. The information must
314 suffice to ensure that the continued functioning of the modified object
315 code is in no case prevented or interfered with solely because
316 modification has been made.
137 317
138 a) Accompany it with the complete corresponding machine-readable
139 source code, which must be distributed under the terms of Sections
140 1 and 2 above on a medium customarily used for software interchange; or,
318 If you convey an object code work under this section in, or with, or
319 specifically for use in, a User Product, and the conveying occurs as
320 part of a transaction in which the right of possession and use of the
321 User Product is transferred to the recipient in perpetuity or for a
322 fixed term (regardless of how the transaction is characterized), the
323 Corresponding Source conveyed under this section must be accompanied
324 by the Installation Information. But this requirement does not apply
325 if neither you nor any third party retains the ability to install
326 modified object code on the User Product (for example, the work has
327 been installed in ROM).
328
329 The requirement to provide Installation Information does not include a
330 requirement to continue to provide support service, warranty, or updates
331 for a work that has been modified or installed by the recipient, or for
332 the User Product in which it has been modified or installed. Access to a
333 network may be denied when the modification itself materially and
334 adversely affects the operation of the network or violates the rules and
335 protocols for communication across the network.
336
337 Corresponding Source conveyed, and Installation Information provided,
338 in accord with this section must be in a format that is publicly
339 documented (and with an implementation available to the public in
340 source code form), and must require no special password or key for
341 unpacking, reading or copying.
342
343 7. Additional Terms.
141 344
142 b) Accompany it with a written offer, valid for at least three
143 years, to give any third party, for a charge no more than your
144 cost of physically performing source distribution, a complete
145 machine-readable copy of the corresponding source code, to be
146 distributed under the terms of Sections 1 and 2 above on a medium
147 customarily used for software interchange; or,
345 "Additional permissions" are terms that supplement the terms of this
346 License by making exceptions from one or more of its conditions.
347 Additional permissions that are applicable to the entire Program shall
348 be treated as though they were included in this License, to the extent
349 that they are valid under applicable law. If additional permissions
350 apply only to part of the Program, that part may be used separately
351 under those permissions, but the entire Program remains governed by
352 this License without regard to the additional permissions.
353
354 When you convey a copy of a covered work, you may at your option
355 remove any additional permissions from that copy, or from any part of
356 it. (Additional permissions may be written to require their own
357 removal in certain cases when you modify the work.) You may place
358 additional permissions on material, added by you to a covered work,
359 for which you have or can give appropriate copyright permission.
148 360
149 c) Accompany it with the information you received as to the offer
150 to distribute corresponding source code. (This alternative is
151 allowed only for noncommercial distribution and only if you
152 received the program in object code or executable form with such
153 an offer, in accord with Subsection b above.)
361 Notwithstanding any other provision of this License, for material you
362 add to a covered work, you may (if authorized by the copyright holders of
363 that material) supplement the terms of this License with terms:
364
365 a) Disclaiming warranty or limiting liability differently from the
366 terms of sections 15 and 16 of this License; or
367
368 b) Requiring preservation of specified reasonable legal notices or
369 author attributions in that material or in the Appropriate Legal
370 Notices displayed by works containing it; or
371
372 c) Prohibiting misrepresentation of the origin of that material, or
373 requiring that modified versions of such material be marked in
374 reasonable ways as different from the original version; or
375
376 d) Limiting the use for publicity purposes of names of licensors or
377 authors of the material; or
154 378
155 The source code for a work means the preferred form of the work for
156 making modifications to it. For an executable work, complete source
157 code means all the source code for all modules it contains, plus any
158 associated interface definition files, plus the scripts used to
159 control compilation and installation of the executable. However, as a
160 special exception, the source code distributed need not include
161 anything that is normally distributed (in either source or binary
162 form) with the major components (compiler, kernel, and so on) of the
163 operating system on which the executable runs, unless that component
164 itself accompanies the executable.
379 e) Declining to grant rights under trademark law for use of some
380 trade names, trademarks, or service marks; or
381
382 f) Requiring indemnification of licensors and authors of that
383 material by anyone who conveys the material (or modified versions of
384 it) with contractual assumptions of liability to the recipient, for
385 any liability that these contractual assumptions directly impose on
386 those licensors and authors.
387
388 All other non-permissive additional terms are considered "further
389 restrictions" within the meaning of section 10. If the Program as you
390 received it, or any part of it, contains a notice stating that it is
391 governed by this License along with a term that is a further
392 restriction, you may remove that term. If a license document contains
393 a further restriction but permits relicensing or conveying under this
394 License, you may add to a covered work material governed by the terms
395 of that license document, provided that the further restriction does
396 not survive such relicensing or conveying.
397
398 If you add terms to a covered work in accord with this section, you
399 must place, in the relevant source files, a statement of the
400 additional terms that apply to those files, or a notice indicating
401 where to find the applicable terms.
165 402
166 If distribution of executable or object code is made by offering
167 access to copy from a designated place, then offering equivalent
168 access to copy the source code from the same place counts as
169 distribution of the source code, even though third parties are not
170 compelled to copy the source along with the object code.
403 Additional terms, permissive or non-permissive, may be stated in the
404 form of a separately written license, or stated as exceptions;
405 the above requirements apply either way.
406
407 8. Termination.
408
409 You may not propagate or modify a covered work except as expressly
410 provided under this License. Any attempt otherwise to propagate or
411 modify it is void, and will automatically terminate your rights under
412 this License (including any patent licenses granted under the third
413 paragraph of section 11).
414
415 However, if you cease all violation of this License, then your
416 license from a particular copyright holder is reinstated (a)
417 provisionally, unless and until the copyright holder explicitly and
418 finally terminates your license, and (b) permanently, if the copyright
419 holder fails to notify you of the violation by some reasonable means
420 prior to 60 days after the cessation.
421
422 Moreover, your license from a particular copyright holder is
423 reinstated permanently if the copyright holder notifies you of the
424 violation by some reasonable means, this is the first time you have
425 received notice of violation of this License (for any work) from that
426 copyright holder, and you cure the violation prior to 30 days after
427 your receipt of the notice.
171 428
172 4. You may not copy, modify, sublicense, or distribute the Program
173 except as expressly provided under this License. Any attempt
174 otherwise to copy, modify, sublicense or distribute the Program is
175 void, and will automatically terminate your rights under this License.
176 However, parties who have received copies, or rights, from you under
177 this License will not have their licenses terminated so long as such
178 parties remain in full compliance.
429 Termination of your rights under this section does not terminate the
430 licenses of parties who have received copies or rights from you under
431 this License. If your rights have been terminated and not permanently
432 reinstated, you do not qualify to receive new licenses for the same
433 material under section 10.
434
435 9. Acceptance Not Required for Having Copies.
436
437 You are not required to accept this License in order to receive or
438 run a copy of the Program. Ancillary propagation of a covered work
439 occurring solely as a consequence of using peer-to-peer transmission
440 to receive a copy likewise does not require acceptance. However,
441 nothing other than this License grants you permission to propagate or
442 modify any covered work. These actions infringe copyright if you do
443 not accept this License. Therefore, by modifying or propagating a
444 covered work, you indicate your acceptance of this License to do so.
445
446 10. Automatic Licensing of Downstream Recipients.
447
448 Each time you convey a covered work, the recipient automatically
449 receives a license from the original licensors, to run, modify and
450 propagate that work, subject to this License. You are not responsible
451 for enforcing compliance by third parties with this License.
179 452
180 5. You are not required to accept this License, since you have not
181 signed it. However, nothing else grants you permission to modify or
182 distribute the Program or its derivative works. These actions are
183 prohibited by law if you do not accept this License. Therefore, by
184 modifying or distributing the Program (or any work based on the
185 Program), you indicate your acceptance of this License to do so, and
186 all its terms and conditions for copying, distributing or modifying
187 the Program or works based on it.
453 An "entity transaction" is a transaction transferring control of an
454 organization, or substantially all assets of one, or subdividing an
455 organization, or merging organizations. If propagation of a covered
456 work results from an entity transaction, each party to that
457 transaction who receives a copy of the work also receives whatever
458 licenses to the work the party's predecessor in interest had or could
459 give under the previous paragraph, plus a right to possession of the
460 Corresponding Source of the work from the predecessor in interest, if
461 the predecessor has it or can get it with reasonable efforts.
188 462
189 6. Each time you redistribute the Program (or any work based on the
190 Program), the recipient automatically receives a license from the
191 original licensor to copy, distribute or modify the Program subject to
192 these terms and conditions. You may not impose any further
193 restrictions on the recipients' exercise of the rights granted herein.
194 You are not responsible for enforcing compliance by third parties to
463 You may not impose any further restrictions on the exercise of the
464 rights granted or affirmed under this License. For example, you may
465 not impose a license fee, royalty, or other charge for exercise of
466 rights granted under this License, and you may not initiate litigation
467 (including a cross-claim or counterclaim in a lawsuit) alleging that
468 any patent claim is infringed by making, using, selling, offering for
469 sale, or importing the Program or any portion of it.
470
471 11. Patents.
472
473 A "contributor" is a copyright holder who authorizes use under this
474 License of the Program or a work on which the Program is based. The
475 work thus licensed is called the contributor's "contributor version".
476
477 A contributor's "essential patent claims" are all patent claims
478 owned or controlled by the contributor, whether already acquired or
479 hereafter acquired, that would be infringed by some manner, permitted
480 by this License, of making, using, or selling its contributor version,
481 but do not include claims that would be infringed only as a
482 consequence of further modification of the contributor version. For
483 purposes of this definition, "control" includes the right to grant
484 patent sublicenses in a manner consistent with the requirements of
195 485 this License.
196 486
197 7. If, as a consequence of a court judgment or allegation of patent
198 infringement or for any other reason (not limited to patent issues),
199 conditions are imposed on you (whether by court order, agreement or
200 otherwise) that contradict the conditions of this License, they do not
201 excuse you from the conditions of this License. If you cannot
202 distribute so as to satisfy simultaneously your obligations under this
203 License and any other pertinent obligations, then as a consequence you
204 may not distribute the Program at all. For example, if a patent
205 license would not permit royalty-free redistribution of the Program by
206 all those who receive copies directly or indirectly through you, then
207 the only way you could satisfy both it and this License would be to
208 refrain entirely from distribution of the Program.
487 Each contributor grants you a non-exclusive, worldwide, royalty-free
488 patent license under the contributor's essential patent claims, to
489 make, use, sell, offer for sale, import and otherwise run, modify and
490 propagate the contents of its contributor version.
491
492 In the following three paragraphs, a "patent license" is any express
493 agreement or commitment, however denominated, not to enforce a patent
494 (such as an express permission to practice a patent or covenant not to
495 sue for patent infringement). To "grant" such a patent license to a
496 party means to make such an agreement or commitment not to enforce a
497 patent against the party.
209 498
210 If any portion of this section is held invalid or unenforceable under
211 any particular circumstance, the balance of the section is intended to
212 apply and the section as a whole is intended to apply in other
213 circumstances.
499 If you convey a covered work, knowingly relying on a patent license,
500 and the Corresponding Source of the work is not available for anyone
501 to copy, free of charge and under the terms of this License, through a
502 publicly available network server or other readily accessible means,
503 then you must either (1) cause the Corresponding Source to be so
504 available, or (2) arrange to deprive yourself of the benefit of the
505 patent license for this particular work, or (3) arrange, in a manner
506 consistent with the requirements of this License, to extend the patent
507 license to downstream recipients. "Knowingly relying" means you have
508 actual knowledge that, but for the patent license, your conveying the
509 covered work in a country, or your recipient's use of the covered work
510 in a country, would infringe one or more identifiable patents in that
511 country that you have reason to believe are valid.
512
513 If, pursuant to or in connection with a single transaction or
514 arrangement, you convey, or propagate by procuring conveyance of, a
515 covered work, and grant a patent license to some of the parties
516 receiving the covered work authorizing them to use, propagate, modify
517 or convey a specific copy of the covered work, then the patent license
518 you grant is automatically extended to all recipients of the covered
519 work and works based on it.
214 520
215 It is not the purpose of this section to induce you to infringe any
216 patents or other property right claims or to contest validity of any
217 such claims; this section has the sole purpose of protecting the
218 integrity of the free software distribution system, which is
219 implemented by public license practices. Many people have made
220 generous contributions to the wide range of software distributed
221 through that system in reliance on consistent application of that
222 system; it is up to the author/donor to decide if he or she is willing
223 to distribute software through any other system and a licensee cannot
224 impose that choice.
521 A patent license is "discriminatory" if it does not include within
522 the scope of its coverage, prohibits the exercise of, or is
523 conditioned on the non-exercise of one or more of the rights that are
524 specifically granted under this License. You may not convey a covered
525 work if you are a party to an arrangement with a third party that is
526 in the business of distributing software, under which you make payment
527 to the third party based on the extent of your activity of conveying
528 the work, and under which the third party grants, to any of the
529 parties who would receive the covered work from you, a discriminatory
530 patent license (a) in connection with copies of the covered work
531 conveyed by you (or copies made from those copies), or (b) primarily
532 for and in connection with specific products or compilations that
533 contain the covered work, unless you entered into that arrangement,
534 or that patent license was granted, prior to 28 March 2007.
535
536 Nothing in this License shall be construed as excluding or limiting
537 any implied license or other defenses to infringement that may
538 otherwise be available to you under applicable patent law.
539
540 12. No Surrender of Others' Freedom.
225 541
226 This section is intended to make thoroughly clear what is believed to
227 be a consequence of the rest of this License.
542 If conditions are imposed on you (whether by court order, agreement or
543 otherwise) that contradict the conditions of this License, they do not
544 excuse you from the conditions of this License. If you cannot convey a
545 covered work so as to satisfy simultaneously your obligations under this
546 License and any other pertinent obligations, then as a consequence you may
547 not convey it at all. For example, if you agree to terms that obligate you
548 to collect a royalty for further conveying from those to whom you convey
549 the Program, the only way you could satisfy both those terms and this
550 License would be to refrain entirely from conveying the Program.
551
552 13. Use with the GNU Affero General Public License.
228 553
229 8. If the distribution and/or use of the Program is restricted in
230 certain countries either by patents or by copyrighted interfaces, the
231 original copyright holder who places the Program under this License
232 may add an explicit geographical distribution limitation excluding
233 those countries, so that distribution is permitted only in or among
234 countries not thus excluded. In such case, this License incorporates
235 the limitation as if written in the body of this License.
554 Notwithstanding any other provision of this License, you have
555 permission to link or combine any covered work with a work licensed
556 under version 3 of the GNU Affero General Public License into a single
557 combined work, and to convey the resulting work. The terms of this
558 License will continue to apply to the part which is the covered work,
559 but the special requirements of the GNU Affero General Public License,
560 section 13, concerning interaction through a network will apply to the
561 combination as such.
236 562
237 9. The Free Software Foundation may publish revised and/or new versions
238 of the General Public License from time to time. Such new versions will
563 14. Revised Versions of this License.
564
565 The Free Software Foundation may publish revised and/or new versions of
566 the GNU General Public License from time to time. Such new versions will
239 567 be similar in spirit to the present version, but may differ in detail to
240 568 address new problems or concerns.
241 569
242 Each version is given a distinguishing version number. If the Program
243 specifies a version number of this License which applies to it and "any
244 later version", you have the option of following the terms and conditions
245 either of that version or of any later version published by the Free
246 Software Foundation. If the Program does not specify a version number of
247 this License, you may choose any version ever published by the Free Software
248 Foundation.
570 Each version is given a distinguishing version number. If the
571 Program specifies that a certain numbered version of the GNU General
572 Public License "or any later version" applies to it, you have the
573 option of following the terms and conditions either of that numbered
574 version or of any later version published by the Free Software
575 Foundation. If the Program does not specify a version number of the
576 GNU General Public License, you may choose any version ever published
577 by the Free Software Foundation.
249 578
250 10. If you wish to incorporate parts of the Program into other free
251 programs whose distribution conditions are different, write to the author
252 to ask for permission. For software which is copyrighted by the Free
253 Software Foundation, write to the Free Software Foundation; we sometimes
254 make exceptions for this. Our decision will be guided by the two goals
255 of preserving the free status of all derivatives of our free software and
256 of promoting the sharing and reuse of software generally.
579 If the Program specifies that a proxy can decide which future
580 versions of the GNU General Public License can be used, that proxy's
581 public statement of acceptance of a version permanently authorizes you
582 to choose that version for the Program.
257 583
258 NO WARRANTY
584 Later license versions may give you additional or different
585 permissions. However, no additional obligations are imposed on any
586 author or copyright holder as a result of your choosing to follow a
587 later version.
588
589 15. Disclaimer of Warranty.
259 590
260 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 REPAIR OR CORRECTION.
591 THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
600 16. Limitation of Liability.
269 601
270 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 POSSIBILITY OF SUCH DAMAGES.
602 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 SUCH DAMAGES.
611
612 17. Interpretation of Sections 15 and 16.
613
614 If the disclaimer of warranty and limitation of liability provided
615 above cannot be given local legal effect according to their terms,
616 reviewing courts shall apply local law that most closely approximates
617 an absolute waiver of all civil liability in connection with the
618 Program, unless a warranty or assumption of liability accompanies a
619 copy of the Program in return for a fee.
279 620
280 621 END OF TERMS AND CONDITIONS
281 622
282 623 How to Apply These Terms to Your New Programs
283 624
284 625 If you develop a new program, and you want it to be of the greatest
285 626 possible use to the public, the best way to achieve this is to make it
286 627 free software which everyone can redistribute and change under these terms.
287 628
288 629 To do so, attach the following notices to the program. It is safest
289 630 to attach them to the start of each source file to most effectively
290 convey the exclusion of warranty; and each file should have at least
631 state the exclusion of warranty; and each file should have at least
291 632 the "copyright" line and a pointer to where the full notice is found.
292 633
293 634 <one line to give the program's name and a brief idea of what it does.>
294 635 Copyright (C) <year> <name of author>
295 636
296 This program is free software; you can redistribute it and/or modify
637 This program is free software: you can redistribute it and/or modify
297 638 it under the terms of the GNU General Public License as published by
298 the Free Software Foundation; either version 2 of the License, or
639 the Free Software Foundation, either version 3 of the License, or
299 640 (at your option) any later version.
300 641
301 642 This program is distributed in the hope that it will be useful,
302 643 but WITHOUT ANY WARRANTY; without even the implied warranty of
303 644 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 645 GNU General Public License for more details.
305 646
306 647 You should have received a copy of the GNU General Public License
307 along with this program; if not, write to the Free Software
308 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
309
648 along with this program. If not, see <http://www.gnu.org/licenses/>.
310 649
311 650 Also add information on how to contact you by electronic and paper mail.
312 651
313 If the program is interactive, make it output a short notice like this
314 when it starts in an interactive mode:
652 If the program does terminal interaction, make it output a short
653 notice like this when it starts in an interactive mode:
315 654
316 Gnomovision version 69, Copyright (C) year name of author
317 Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
655 <program> Copyright (C) <year> <name of author>
656 This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
318 657 This is free software, and you are welcome to redistribute it
319 658 under certain conditions; type `show c' for details.
320 659
321 660 The hypothetical commands `show w' and `show c' should show the appropriate
322 parts of the General Public License. Of course, the commands you use may
323 be called something other than `show w' and `show c'; they could even be
324 mouse-clicks or menu items--whatever suits your program.
325
326 You should also get your employer (if you work as a programmer) or your
327 school, if any, to sign a "copyright disclaimer" for the program, if
328 necessary. Here is a sample; alter the names:
661 parts of the General Public License. Of course, your program's commands
662 might be different; for a GUI interface, you would use an "about box".
329 663
330 Yoyodyne, Inc., hereby disclaims all copyright interest in the program
331 `Gnomovision' (which makes passes at compilers) written by James Hacker.
332
333 <signature of Ty Coon>, 1 April 1989
334 Ty Coon, President of Vice
664 You should also get your employer (if you work as a programmer) or school,
665 if any, to sign a "copyright disclaimer" for the program, if necessary.
666 For more information on this, and how to apply and follow the GNU GPL, see
667 <http://www.gnu.org/licenses/>.
335 668
336 This General Public License does not permit incorporating your program into
337 proprietary programs. If your program is a subroutine library, you may
338 consider it more useful to permit linking proprietary applications with the
339 library. If this is what you want to do, use the GNU Library General
340 Public License instead of this License.
669 The GNU General Public License does not permit incorporating your program
670 into proprietary programs. If your program is a subroutine library, you
671 may consider it more useful to permit linking proprietary applications with
672 the library. If this is what you want to do, use the GNU Lesser General
673 Public License instead of this License. But first, please read
674 <http://www.gnu.org/philosophy/why-not-lgpl.html>.
@@ -1,148 +1,147 b''
1 1
2 2 =================================================
3 3 Welcome to RhodeCode (RhodiumCode) documentation!
4 4 =================================================
5 5
6 6 ``RhodeCode`` (formerly hg-app) is a Pylons framework based Mercurial repository
7 7 browser/management tool with a built in push/pull server and full text search.
8 8 It works on http/https and has a built in permission/authentication system with
9 9 the ability to authenticate via LDAP.
10 10
11 11 RhodeCode is similar in some respects to github or bitbucket_,
12 12 however RhodeCode can be run as standalone hosted application on your own server.
13 13 It is open source and donation ware and focuses more on providing a customized,
14 14 self administered interface for Mercurial(and soon GIT) repositories.
15 15 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
16 16 handle multiple different version control systems.
17 17
18 18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
19 19
20 20 RhodeCode demo
21 21 --------------
22 22
23 23 http://demo.rhodecode.org
24 24
25 25 The default access is anonymous but you can login to an administrative account
26 26 using the following credentials:
27 27
28 28 - username: demo
29 29 - password: demo
30 30
31 31 Source code
32 32 -----------
33 33
34 34 The latest sources can be obtained from official RhodeCode instance
35 35 https://hg.rhodecode.org
36 36
37 37
38 38 MIRRORS:
39 39
40 40 Issue tracker and sources at bitbucket_
41 41
42 42 http://bitbucket.org/marcinkuzminski/rhodecode
43 43
44 44 Sources at github_
45 45
46 46 https://github.com/marcinkuzminski/rhodecode
47 47
48 48 Installation
49 49 ------------
50 50
51 51 Please visit http://packages.python.org/RhodeCode/installation.html
52 52
53 53
54 54 RhodeCode Features
55 55 ------------------
56 56
57 57 - Has it's own middleware to handle mercurial_ protocol requests.
58 58 Each request can be logged and authenticated.
59 59 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
60 60 Supports http/https and LDAP
61 61 - Full permissions (private/read/write/admin) and authentication per project.
62 62 One account for web interface and mercurial_ push/pull/clone operations.
63 63 - Have built in users groups for easier permission management
64 64 - Users can fork other users repo. RhodeCode have also compare view to see
65 65 combined changeset for all changeset made within single push.
66 66 - Mako templates let's you customize the look and feel of the application.
67 67 - Beautiful diffs, annotations and source code browsing all colored by pygments.
68 68 Raw diffs are made in git-diff format, including git_ binary-patches
69 69 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
70 70 - Admin interface with user/permission management. Admin activity journal, logs
71 71 pulls, pushes, forks, registrations and other actions made by all users.
72 72 - Server side forks. It is possible to fork a project and modify it freely
73 73 without breaking the main repository. You can even write Your own hooks
74 74 and install them
75 75 - Full text search powered by Whoosh on the source files, and file names.
76 76 Build in indexing daemons, with optional incremental index build
77 77 (no external search servers required all in one application)
78 78 - Setup project descriptions and info inside built in db for easy, non
79 79 file-system operations
80 80 - Intelligent cache with invalidation after push or project change, provides
81 81 high performance and always up to date data.
82 82 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
83 83 - Async tasks for speed and performance using celery_ (works without them too)
84 84 - Backup scripts can do backup of whole app and send it over scp to desired
85 85 location
86 86 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
87 87
88 88
89 89 .. include:: ./docs/screenshots.rst
90 90
91 91
92 92 Incoming / Plans
93 93 ----------------
94 94
95 95 - Project grouping
96 96 - Server side tagging
97 97 - Server side code edit
98 98 - Finer granular permissions (per branch or subrepo)
99 99 - SSH based authentication with server side key management
100 100 - Code review (probably based on hg-review)
101 101 - Full git_ support, with push/pull server (currently in beta tests)
102 102 - Redmine integration
103 103 - Commit based built in wiki system
104 104 - More statistics and graph (global annotation + some more statistics)
105 105 - Other advancements as development continues (or you can of course make
106 106 additions and or requests)
107 107
108 108 License
109 109 -------
110 110
111 ``RhodeCode`` is released under the GPL_ license.
111 ``RhodeCode`` is released under the GPLv3 license.
112 112
113 113
114 114 Mailing group Q&A
115 115 -----------------
116 116
117 117 Join the `Google group <http://groups.google.com/group/rhodecode>`_
118 118
119 119 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
120 120
121 121 Join #rhodecode on FreeNode (irc.freenode.net)
122 122 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
123 123
124 124 Online documentation
125 125 --------------------
126 126
127 127 Online documentation for the current version of RhodeCode is available at
128 128 http://packages.python.org/RhodeCode/.
129 129 You may also build the documentation for yourself - go into ``docs/`` and run::
130 130
131 131 make html
132 132
133 133 (You need to have sphinx_ installed to build the documentation. If you don't
134 134 have sphinx_ installed you can install it via the command:
135 135 ``easy_install sphinx``)
136 136
137 137 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
138 138 .. _python: http://www.python.org/
139 139 .. _sphinx: http://sphinx.pocoo.org/
140 140 .. _mercurial: http://mercurial.selenic.com/
141 141 .. _bitbucket: http://bitbucket.org/
142 142 .. _github: http://github.com/
143 143 .. _subversion: http://subversion.tigris.org/
144 144 .. _git: http://git-scm.com/
145 145 .. _celery: http://celeryproject.org/
146 146 .. _Sphinx: http://sphinx.pocoo.org/
147 .. _GPL: http://www.gnu.org/licenses/gpl.html
148 147 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
@@ -1,60 +1,59 b''
1 1 .. _index:
2 2
3 3 .. include:: ./../README.rst
4 4
5 5 Documentation
6 6 -------------
7 7
8 8 **Installation:**
9 9
10 10 .. toctree::
11 11 :maxdepth: 1
12 12
13 13 installation
14 14 setup
15 15 upgrade
16 16
17 17 **Usage**
18 18
19 19 .. toctree::
20 20 :maxdepth: 1
21 21
22 22 usage/general
23 23 usage/enable_git
24 24 usage/statistics
25 25 usage/backup
26 26 usage/api_key_access
27 27
28 28 **Develop**
29 29
30 30 .. toctree::
31 31 :maxdepth: 1
32 32
33 33 contributing
34 34 changelog
35 35
36 36 **API**
37 37
38 38 .. toctree::
39 39 :maxdepth: 2
40 40
41 41 api/index
42 42
43 43
44 44 Other topics
45 45 ------------
46 46
47 47 * :ref:`genindex`
48 48 * :ref:`search`
49 49
50 50 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
51 51 .. _python: http://www.python.org/
52 52 .. _django: http://www.djangoproject.com/
53 53 .. _mercurial: http://mercurial.selenic.com/
54 54 .. _bitbucket: http://bitbucket.org/
55 55 .. _subversion: http://subversion.tigris.org/
56 56 .. _git: http://git-scm.com/
57 57 .. _celery: http://celeryproject.org/
58 58 .. _Sphinx: http://sphinx.pocoo.org/
59 .. _GPL: http://www.gnu.org/licenses/gpl.html
60 59 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
@@ -1,58 +1,56 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.__init__
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode, a web based repository management based on pylons
7 7 versioning implementation: http://semver.org/
8 8
9 9 :created_on: Apr 9, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 26 import platform
29 27
30 28 VERSION = (1, 2, 0, 'beta')
31 29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
32 30 __dbversion__ = 3 #defines current db version for migrations
33 31 __platform__ = platform.system()
34 32 __license__ = 'GPLv3'
35 33
36 34 PLATFORM_WIN = ('Windows')
37 35 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD')
38 36
39 37 try:
40 38 from rhodecode.lib.utils import get_current_revision
41 39 _rev = get_current_revision()
42 40 except ImportError:
43 41 #this is needed when doing some setup.py operations
44 42 _rev = False
45 43
46 44 if len(VERSION) > 3 and _rev:
47 45 __version__ += ' [rev:%s]' % _rev[0]
48 46
49 47
50 48 def get_version():
51 49 """Returns shorter version (digit parts only) as string."""
52 50
53 51 return '.'.join((str(each) for each in VERSION[:3]))
54 52
55 53 BACKENDS = {
56 54 'hg': 'Mercurial repository',
57 55 #'git': 'Git repository',
58 56 }
@@ -1,59 +1,57 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.admin
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Controller for Admin panel of Rhodecode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27
30 28 from pylons import request, tmpl_context as c
31 29 from sqlalchemy.orm import joinedload
32 30 from webhelpers.paginate import Page
33 31
34 32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
35 33 from rhodecode.lib.base import BaseController, render
36 34 from rhodecode.model.db import UserLog
37 35
38 36 log = logging.getLogger(__name__)
39 37
40 38 class AdminController(BaseController):
41 39
42 40 @LoginRequired()
43 41 def __before__(self):
44 42 super(AdminController, self).__before__()
45 43
46 44 @HasPermissionAllDecorator('hg.admin')
47 45 def index(self):
48 46
49 47 users_log = self.sa.query(UserLog)\
50 48 .options(joinedload(UserLog.user))\
51 49 .options(joinedload(UserLog.repository))\
52 50 .order_by(UserLog.action_date.desc())
53 51
54 52 p = int(request.params.get('page', 1))
55 53 c.users_log = Page(users_log, page=p, items_per_page=10)
56 54 c.log_data = render('admin/admin_log.html')
57 55 if request.params.get('partial'):
58 56 return c.log_data
59 57 return render('admin/admin.html')
@@ -1,127 +1,125 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.ldap_settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 ldap controller for RhodeCode
7 7
8 8 :created_on: Nov 26, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25 import logging
28 26 import formencode
29 27 import traceback
30 28
31 29 from formencode import htmlfill
32 30
33 31 from pylons import request, response, session, tmpl_context as c, url
34 32 from pylons.controllers.util import abort, redirect
35 33 from pylons.i18n.translation import _
36 34
37 35 from rhodecode.lib.base import BaseController, render
38 36 from rhodecode.lib import helpers as h
39 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
40 38 from rhodecode.lib.auth_ldap import LdapImportError
41 39 from rhodecode.model.settings import SettingsModel
42 40 from rhodecode.model.forms import LdapSettingsForm
43 41 from sqlalchemy.exc import DatabaseError
44 42
45 43 log = logging.getLogger(__name__)
46 44
47 45
48 46
49 47 class LdapSettingsController(BaseController):
50 48
51 49 search_scope_choices = [('BASE', _('BASE'),),
52 50 ('ONELEVEL', _('ONELEVEL'),),
53 51 ('SUBTREE', _('SUBTREE'),),
54 52 ]
55 53 search_scope_default = 'SUBTREE'
56 54
57 55 tls_reqcert_choices = [('NEVER', _('NEVER'),),
58 56 ('ALLOW', _('ALLOW'),),
59 57 ('TRY', _('TRY'),),
60 58 ('DEMAND', _('DEMAND'),),
61 59 ('HARD', _('HARD'),),
62 60 ]
63 61 tls_reqcert_default = 'DEMAND'
64 62
65 63 @LoginRequired()
66 64 @HasPermissionAllDecorator('hg.admin')
67 65 def __before__(self):
68 66 c.admin_user = session.get('admin_user')
69 67 c.admin_username = session.get('admin_username')
70 68 c.search_scope_choices = self.search_scope_choices
71 69 c.tls_reqcert_choices = self.tls_reqcert_choices
72 70 super(LdapSettingsController, self).__before__()
73 71
74 72 def index(self):
75 73 defaults = SettingsModel().get_ldap_settings()
76 74 c.search_scope_cur = defaults.get('ldap_search_scope')
77 75 c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
78 76
79 77 return htmlfill.render(
80 78 render('admin/ldap/ldap.html'),
81 79 defaults=defaults,
82 80 encoding="UTF-8",
83 81 force_defaults=True,)
84 82
85 83 def ldap_settings(self):
86 84 """POST ldap create and store ldap settings"""
87 85
88 86 settings_model = SettingsModel()
89 87 _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
90 88 [x[0] for x in self.search_scope_choices])()
91 89
92 90 try:
93 91 form_result = _form.to_python(dict(request.POST))
94 92 try:
95 93
96 94 for k, v in form_result.items():
97 95 if k.startswith('ldap_'):
98 96 setting = settings_model.get(k)
99 97 setting.app_settings_value = v
100 98 self.sa.add(setting)
101 99
102 100 self.sa.commit()
103 101 h.flash(_('Ldap settings updated successfully'),
104 102 category='success')
105 103 except (DatabaseError,):
106 104 raise
107 105 except LdapImportError:
108 106 h.flash(_('Unable to activate ldap. The "python-ldap" library '
109 107 'is missing.'), category='warning')
110 108
111 109 except formencode.Invalid, errors:
112 110
113 111 c.search_scope_cur = self.search_scope_default
114 112 c.tls_reqcert_cur = self.search_scope_default
115 113
116 114 return htmlfill.render(
117 115 render('admin/ldap/ldap.html'),
118 116 defaults=errors.value,
119 117 errors=errors.error_dict or {},
120 118 prefix_error=False,
121 119 encoding="UTF-8")
122 120 except Exception:
123 121 log.error(traceback.format_exc())
124 122 h.flash(_('error occurred during update of ldap settings'),
125 123 category='error')
126 124
127 125 return redirect(url('ldap_home'))
@@ -1,171 +1,169 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.permissions
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 permissions controller for Rhodecode
7 7
8 8 :created_on: Apr 27, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 from formencode import htmlfill
29 27 from pylons import request, session, tmpl_context as c, url
30 28 from pylons.controllers.util import abort, redirect
31 29 from pylons.i18n.translation import _
32 30 from rhodecode.lib import helpers as h
33 31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 32 from rhodecode.lib.auth_ldap import LdapImportError
35 33 from rhodecode.lib.base import BaseController, render
36 34 from rhodecode.model.forms import LdapSettingsForm, DefaultPermissionsForm
37 35 from rhodecode.model.permission import PermissionModel
38 36 from rhodecode.model.settings import SettingsModel
39 37 from rhodecode.model.user import UserModel
40 38 import formencode
41 39 import logging
42 40 import traceback
43 41
44 42 log = logging.getLogger(__name__)
45 43
46 44 class PermissionsController(BaseController):
47 45 """REST Controller styled on the Atom Publishing Protocol"""
48 46 # To properly map this controller, ensure your config/routing.py
49 47 # file has a resource setup:
50 48 # map.resource('permission', 'permissions')
51 49
52 50 @LoginRequired()
53 51 @HasPermissionAllDecorator('hg.admin')
54 52 def __before__(self):
55 53 c.admin_user = session.get('admin_user')
56 54 c.admin_username = session.get('admin_username')
57 55 super(PermissionsController, self).__before__()
58 56
59 57 self.perms_choices = [('repository.none', _('None'),),
60 58 ('repository.read', _('Read'),),
61 59 ('repository.write', _('Write'),),
62 60 ('repository.admin', _('Admin'),)]
63 61 self.register_choices = [
64 62 ('hg.register.none',
65 63 _('disabled')),
66 64 ('hg.register.manual_activate',
67 65 _('allowed with manual account activation')),
68 66 ('hg.register.auto_activate',
69 67 _('allowed with automatic account activation')), ]
70 68
71 69 self.create_choices = [('hg.create.none', _('Disabled')),
72 70 ('hg.create.repository', _('Enabled'))]
73 71
74 72
75 73 def index(self, format='html'):
76 74 """GET /permissions: All items in the collection"""
77 75 # url('permissions')
78 76
79 77 def create(self):
80 78 """POST /permissions: Create a new item"""
81 79 # url('permissions')
82 80
83 81 def new(self, format='html'):
84 82 """GET /permissions/new: Form to create a new item"""
85 83 # url('new_permission')
86 84
87 85 def update(self, id):
88 86 """PUT /permissions/id: Update an existing item"""
89 87 # Forms posted to this method should contain a hidden field:
90 88 # <input type="hidden" name="_method" value="PUT" />
91 89 # Or using helpers:
92 90 # h.form(url('permission', id=ID),
93 91 # method='put')
94 92 # url('permission', id=ID)
95 93
96 94 permission_model = PermissionModel()
97 95
98 96 _form = DefaultPermissionsForm([x[0] for x in self.perms_choices],
99 97 [x[0] for x in self.register_choices],
100 98 [x[0] for x in self.create_choices])()
101 99
102 100 try:
103 101 form_result = _form.to_python(dict(request.POST))
104 102 form_result.update({'perm_user_name':id})
105 103 permission_model.update(form_result)
106 104 h.flash(_('Default permissions updated successfully'),
107 105 category='success')
108 106
109 107 except formencode.Invalid, errors:
110 108 c.perms_choices = self.perms_choices
111 109 c.register_choices = self.register_choices
112 110 c.create_choices = self.create_choices
113 111 defaults = errors.value
114 112
115 113 return htmlfill.render(
116 114 render('admin/permissions/permissions.html'),
117 115 defaults=defaults,
118 116 errors=errors.error_dict or {},
119 117 prefix_error=False,
120 118 encoding="UTF-8")
121 119 except Exception:
122 120 log.error(traceback.format_exc())
123 121 h.flash(_('error occurred during update of permissions'),
124 122 category='error')
125 123
126 124 return redirect(url('edit_permission', id=id))
127 125
128 126
129 127
130 128 def delete(self, id):
131 129 """DELETE /permissions/id: Delete an existing item"""
132 130 # Forms posted to this method should contain a hidden field:
133 131 # <input type="hidden" name="_method" value="DELETE" />
134 132 # Or using helpers:
135 133 # h.form(url('permission', id=ID),
136 134 # method='delete')
137 135 # url('permission', id=ID)
138 136
139 137 def show(self, id, format='html'):
140 138 """GET /permissions/id: Show a specific item"""
141 139 # url('permission', id=ID)
142 140
143 141 def edit(self, id, format='html'):
144 142 """GET /permissions/id/edit: Form to edit an existing item"""
145 143 #url('edit_permission', id=ID)
146 144 c.perms_choices = self.perms_choices
147 145 c.register_choices = self.register_choices
148 146 c.create_choices = self.create_choices
149 147
150 148 if id == 'default':
151 149 default_user = UserModel().get_by_username('default')
152 150 defaults = {'_method':'put',
153 151 'anonymous':default_user.active}
154 152
155 153 for p in default_user.user_perms:
156 154 if p.permission.permission_name.startswith('repository.'):
157 155 defaults['default_perm'] = p.permission.permission_name
158 156
159 157 if p.permission.permission_name.startswith('hg.register.'):
160 158 defaults['default_register'] = p.permission.permission_name
161 159
162 160 if p.permission.permission_name.startswith('hg.create.'):
163 161 defaults['default_create'] = p.permission.permission_name
164 162
165 163 return htmlfill.render(
166 164 render('admin/permissions/permissions.html'),
167 165 defaults=defaults,
168 166 encoding="UTF-8",
169 167 force_defaults=True,)
170 168 else:
171 169 return redirect(url('admin_home'))
@@ -1,423 +1,421 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Admin controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 import traceback
30 28 import formencode
31 29 from operator import itemgetter
32 30 from formencode import htmlfill
33 31
34 32 from paste.httpexceptions import HTTPInternalServerError
35 33 from pylons import request, response, session, tmpl_context as c, url
36 34 from pylons.controllers.util import abort, redirect
37 35 from pylons.i18n.translation import _
38 36
39 37 from rhodecode.lib import helpers as h
40 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
41 39 HasPermissionAnyDecorator
42 40 from rhodecode.lib.base import BaseController, render
43 41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
44 42 from rhodecode.lib.helpers import get_token
45 43 from rhodecode.model.db import User, Repository, UserFollowing, Group
46 44 from rhodecode.model.forms import RepoForm
47 45 from rhodecode.model.scm import ScmModel
48 46 from rhodecode.model.repo import RepoModel
49 47
50 48 log = logging.getLogger(__name__)
51 49
52 50 class ReposController(BaseController):
53 51 """
54 52 REST Controller styled on the Atom Publishing Protocol"""
55 53 # To properly map this controller, ensure your config/routing.py
56 54 # file has a resource setup:
57 55 # map.resource('repo', 'repos')
58 56
59 57 @LoginRequired()
60 58 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 59 def __before__(self):
62 60 c.admin_user = session.get('admin_user')
63 61 c.admin_username = session.get('admin_username')
64 62 super(ReposController, self).__before__()
65 63
66 64 def __load_defaults(self):
67 65 repo_model = RepoModel()
68 66
69 67 c.repo_groups = [('', '')]
70 68 parents_link = lambda k:h.literal('&raquo;'.join(
71 69 map(lambda k:k.group_name,
72 70 k.parents + [k])
73 71 )
74 72 )
75 73
76 74 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
77 75 x in self.sa.query(Group).all()])
78 76 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
79 77 c.users_array = repo_model.get_users_js()
80 78 c.users_groups_array = repo_model.get_users_groups_js()
81 79
82 80 def __load_data(self, repo_name=None):
83 81 """
84 82 Load defaults settings for edit, and update
85 83
86 84 :param repo_name:
87 85 """
88 86 self.__load_defaults()
89 87
90 88 repo, dbrepo = ScmModel().get(repo_name, retval='repo')
91 89
92 90 repo_model = RepoModel()
93 91 c.repo_info = repo_model.get_by_repo_name(repo_name)
94 92
95 93
96 94 if c.repo_info is None:
97 95 h.flash(_('%s repository is not mapped to db perhaps'
98 96 ' it was created or renamed from the filesystem'
99 97 ' please run the application again'
100 98 ' in order to rescan repositories') % repo_name,
101 99 category='error')
102 100
103 101 return redirect(url('repos'))
104 102
105 103
106 104 c.default_user_id = User.by_username('default').user_id
107 105 c.in_public_journal = self.sa.query(UserFollowing)\
108 106 .filter(UserFollowing.user_id == c.default_user_id)\
109 107 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
110 108
111 109 if c.repo_info.stats:
112 110 last_rev = c.repo_info.stats.stat_on_revision
113 111 else:
114 112 last_rev = 0
115 113 c.stats_revision = last_rev
116 114
117 115 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
118 116
119 117 if last_rev == 0 or c.repo_last_rev == 0:
120 118 c.stats_percentage = 0
121 119 else:
122 120 c.stats_percentage = '%.2f' % ((float((last_rev)) /
123 121 c.repo_last_rev) * 100)
124 122
125 123
126 124
127 125 defaults = c.repo_info.get_dict()
128 126 group, repo_name = c.repo_info.groups_and_repo
129 127 defaults['repo_name'] = repo_name
130 128 defaults['repo_group'] = getattr(group[-1] if group else None,
131 129 'group_id', None)
132 130
133 131 #fill owner
134 132 if c.repo_info.user:
135 133 defaults.update({'user':c.repo_info.user.username})
136 134 else:
137 135 replacement_user = self.sa.query(User)\
138 136 .filter(User.admin == True).first().username
139 137 defaults.update({'user':replacement_user})
140 138
141 139
142 140 #fill repository users
143 141 for p in c.repo_info.repo_to_perm:
144 142 defaults.update({'u_perm_%s' % p.user.username:
145 143 p.permission.permission_name})
146 144
147 145 #fill repository groups
148 146 for p in c.repo_info.users_group_to_perm:
149 147 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
150 148 p.permission.permission_name})
151 149
152 150
153 151 return defaults
154 152
155 153
156 154 @HasPermissionAllDecorator('hg.admin')
157 155 def index(self, format='html'):
158 156 """GET /repos: All items in the collection"""
159 157 # url('repos')
160 158 cached_repo_list = ScmModel().get_repos()
161 159 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
162 160 return render('admin/repos/repos.html')
163 161
164 162 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
165 163 def create(self):
166 164 """
167 165 POST /repos: Create a new item"""
168 166 # url('repos')
169 167 repo_model = RepoModel()
170 168 self.__load_defaults()
171 169 form_result = {}
172 170 try:
173 171 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
174 172 .to_python(dict(request.POST))
175 173 repo_model.create(form_result, self.rhodecode_user)
176 174 if form_result['clone_uri']:
177 175 h.flash(_('created repository %s from %s') \
178 176 % (form_result['repo_name'], form_result['clone_uri']),
179 177 category='success')
180 178 else:
181 179 h.flash(_('created repository %s') % form_result['repo_name'],
182 180 category='success')
183 181
184 182 if request.POST.get('user_created'):
185 183 action_logger(self.rhodecode_user, 'user_created_repo',
186 184 form_result['repo_name'], '', self.sa)
187 185 else:
188 186 action_logger(self.rhodecode_user, 'admin_created_repo',
189 187 form_result['repo_name'], '', self.sa)
190 188
191 189 except formencode.Invalid, errors:
192 190
193 191 c.new_repo = errors.value['repo_name']
194 192
195 193 if request.POST.get('user_created'):
196 194 r = render('admin/repos/repo_add_create_repository.html')
197 195 else:
198 196 r = render('admin/repos/repo_add.html')
199 197
200 198 return htmlfill.render(
201 199 r,
202 200 defaults=errors.value,
203 201 errors=errors.error_dict or {},
204 202 prefix_error=False,
205 203 encoding="UTF-8")
206 204
207 205 except Exception:
208 206 log.error(traceback.format_exc())
209 207 msg = _('error occurred during creation of repository %s') \
210 208 % form_result.get('repo_name')
211 209 h.flash(msg, category='error')
212 210 if request.POST.get('user_created'):
213 211 return redirect(url('home'))
214 212 return redirect(url('repos'))
215 213
216 214 @HasPermissionAllDecorator('hg.admin')
217 215 def new(self, format='html'):
218 216 """GET /repos/new: Form to create a new item"""
219 217 new_repo = request.GET.get('repo', '')
220 218 c.new_repo = repo_name_slug(new_repo)
221 219 self.__load_defaults()
222 220 return render('admin/repos/repo_add.html')
223 221
224 222 @HasPermissionAllDecorator('hg.admin')
225 223 def update(self, repo_name):
226 224 """
227 225 PUT /repos/repo_name: Update an existing item"""
228 226 # Forms posted to this method should contain a hidden field:
229 227 # <input type="hidden" name="_method" value="PUT" />
230 228 # Or using helpers:
231 229 # h.form(url('repo', repo_name=ID),
232 230 # method='put')
233 231 # url('repo', repo_name=ID)
234 232 self.__load_defaults()
235 233 repo_model = RepoModel()
236 234 changed_name = repo_name
237 235 _form = RepoForm(edit=True, old_data={'repo_name':repo_name},
238 236 repo_groups=c.repo_groups_choices)()
239 237 try:
240 238 form_result = _form.to_python(dict(request.POST))
241 239 repo_model.update(repo_name, form_result)
242 240 invalidate_cache('get_repo_cached_%s' % repo_name)
243 241 h.flash(_('Repository %s updated successfully' % repo_name),
244 242 category='success')
245 243 changed_name = form_result['repo_name']
246 244 action_logger(self.rhodecode_user, 'admin_updated_repo',
247 245 changed_name, '', self.sa)
248 246
249 247 except formencode.Invalid, errors:
250 248 defaults = self.__load_data(repo_name)
251 249 defaults.update(errors.value)
252 250 return htmlfill.render(
253 251 render('admin/repos/repo_edit.html'),
254 252 defaults=defaults,
255 253 errors=errors.error_dict or {},
256 254 prefix_error=False,
257 255 encoding="UTF-8")
258 256
259 257 except Exception:
260 258 log.error(traceback.format_exc())
261 259 h.flash(_('error occurred during update of repository %s') \
262 260 % repo_name, category='error')
263 261 return redirect(url('edit_repo', repo_name=changed_name))
264 262
265 263 @HasPermissionAllDecorator('hg.admin')
266 264 def delete(self, repo_name):
267 265 """
268 266 DELETE /repos/repo_name: Delete an existing item"""
269 267 # Forms posted to this method should contain a hidden field:
270 268 # <input type="hidden" name="_method" value="DELETE" />
271 269 # Or using helpers:
272 270 # h.form(url('repo', repo_name=ID),
273 271 # method='delete')
274 272 # url('repo', repo_name=ID)
275 273
276 274 repo_model = RepoModel()
277 275 repo = repo_model.get_by_repo_name(repo_name)
278 276 if not repo:
279 277 h.flash(_('%s repository is not mapped to db perhaps'
280 278 ' it was moved or renamed from the filesystem'
281 279 ' please run the application again'
282 280 ' in order to rescan repositories') % repo_name,
283 281 category='error')
284 282
285 283 return redirect(url('repos'))
286 284 try:
287 285 action_logger(self.rhodecode_user, 'admin_deleted_repo',
288 286 repo_name, '', self.sa)
289 287 repo_model.delete(repo)
290 288 invalidate_cache('get_repo_cached_%s' % repo_name)
291 289 h.flash(_('deleted repository %s') % repo_name, category='success')
292 290
293 291 except Exception, e:
294 292 log.error(traceback.format_exc())
295 293 h.flash(_('An error occurred during deletion of %s') % repo_name,
296 294 category='error')
297 295
298 296 return redirect(url('repos'))
299 297
300 298 @HasPermissionAllDecorator('hg.admin')
301 299 def delete_perm_user(self, repo_name):
302 300 """
303 301 DELETE an existing repository permission user
304 302
305 303 :param repo_name:
306 304 """
307 305
308 306 try:
309 307 repo_model = RepoModel()
310 308 repo_model.delete_perm_user(request.POST, repo_name)
311 309 except Exception, e:
312 310 h.flash(_('An error occurred during deletion of repository user'),
313 311 category='error')
314 312 raise HTTPInternalServerError()
315 313
316 314 @HasPermissionAllDecorator('hg.admin')
317 315 def delete_perm_users_group(self, repo_name):
318 316 """
319 317 DELETE an existing repository permission users group
320 318
321 319 :param repo_name:
322 320 """
323 321 try:
324 322 repo_model = RepoModel()
325 323 repo_model.delete_perm_users_group(request.POST, repo_name)
326 324 except Exception, e:
327 325 h.flash(_('An error occurred during deletion of repository'
328 326 ' users groups'),
329 327 category='error')
330 328 raise HTTPInternalServerError()
331 329
332 330 @HasPermissionAllDecorator('hg.admin')
333 331 def repo_stats(self, repo_name):
334 332 """
335 333 DELETE an existing repository statistics
336 334
337 335 :param repo_name:
338 336 """
339 337
340 338 try:
341 339 repo_model = RepoModel()
342 340 repo_model.delete_stats(repo_name)
343 341 except Exception, e:
344 342 h.flash(_('An error occurred during deletion of repository stats'),
345 343 category='error')
346 344 return redirect(url('edit_repo', repo_name=repo_name))
347 345
348 346 @HasPermissionAllDecorator('hg.admin')
349 347 def repo_cache(self, repo_name):
350 348 """
351 349 INVALIDATE existing repository cache
352 350
353 351 :param repo_name:
354 352 """
355 353
356 354 try:
357 355 ScmModel().mark_for_invalidation(repo_name)
358 356 except Exception, e:
359 357 h.flash(_('An error occurred during cache invalidation'),
360 358 category='error')
361 359 return redirect(url('edit_repo', repo_name=repo_name))
362 360
363 361 @HasPermissionAllDecorator('hg.admin')
364 362 def repo_public_journal(self, repo_name):
365 363 """
366 364 Set's this repository to be visible in public journal,
367 365 in other words assing default user to follow this repo
368 366
369 367 :param repo_name:
370 368 """
371 369
372 370 cur_token = request.POST.get('auth_token')
373 371 token = get_token()
374 372 if cur_token == token:
375 373 try:
376 374 repo_id = Repository.by_repo_name(repo_name).repo_id
377 375 user_id = User.by_username('default').user_id
378 376 self.scm_model.toggle_following_repo(repo_id, user_id)
379 377 h.flash(_('Updated repository visibility in public journal'),
380 378 category='success')
381 379 except:
382 380 h.flash(_('An error occurred during setting this'
383 381 ' repository in public journal'),
384 382 category='error')
385 383
386 384 else:
387 385 h.flash(_('Token mismatch'), category='error')
388 386 return redirect(url('edit_repo', repo_name=repo_name))
389 387
390 388 @HasPermissionAllDecorator('hg.admin')
391 389 def repo_pull(self, repo_name):
392 390 """
393 391 Runs task to update given repository with remote changes,
394 392 ie. make pull on remote location
395 393
396 394 :param repo_name:
397 395 """
398 396 try:
399 397 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
400 398 h.flash(_('Pulled from remote location'), category='success')
401 399 except Exception, e:
402 400 h.flash(_('An error occurred during pull from remote location'),
403 401 category='error')
404 402
405 403 return redirect(url('edit_repo', repo_name=repo_name))
406 404
407 405 @HasPermissionAllDecorator('hg.admin')
408 406 def show(self, repo_name, format='html'):
409 407 """GET /repos/repo_name: Show a specific item"""
410 408 # url('repo', repo_name=ID)
411 409
412 410 @HasPermissionAllDecorator('hg.admin')
413 411 def edit(self, repo_name, format='html'):
414 412 """GET /repos/repo_name/edit: Form to edit an existing item"""
415 413 # url('edit_repo', repo_name=ID)
416 414 defaults = self.__load_data(repo_name)
417 415
418 416 return htmlfill.render(
419 417 render('admin/repos/repo_edit.html'),
420 418 defaults=defaults,
421 419 encoding="UTF-8",
422 420 force_defaults=False
423 421 )
@@ -1,359 +1,357 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 import traceback
30 28 import formencode
31 29
32 30 from sqlalchemy import func
33 31 from formencode import htmlfill
34 32 from pylons import request, session, tmpl_context as c, url, config
35 33 from pylons.controllers.util import abort, redirect
36 34 from pylons.i18n.translation import _
37 35
38 36 from rhodecode.lib import helpers as h
39 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 38 HasPermissionAnyDecorator, NotAnonymous
41 39 from rhodecode.lib.base import BaseController, render
42 40 from rhodecode.lib.celerylib import tasks, run_task
43 41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
44 42 set_rhodecode_config, repo_name_slug
45 43 from rhodecode.model.db import RhodeCodeUi, Repository, Group
46 44 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
47 45 ApplicationUiSettingsForm
48 46 from rhodecode.model.scm import ScmModel
49 47 from rhodecode.model.settings import SettingsModel
50 48 from rhodecode.model.user import UserModel
51 49
52 50 log = logging.getLogger(__name__)
53 51
54 52
55 53 class SettingsController(BaseController):
56 54 """REST Controller styled on the Atom Publishing Protocol"""
57 55 # To properly map this controller, ensure your config/routing.py
58 56 # file has a resource setup:
59 57 # map.resource('setting', 'settings', controller='admin/settings',
60 58 # path_prefix='/admin', name_prefix='admin_')
61 59
62 60
63 61 @LoginRequired()
64 62 def __before__(self):
65 63 c.admin_user = session.get('admin_user')
66 64 c.admin_username = session.get('admin_username')
67 65 super(SettingsController, self).__before__()
68 66
69 67
70 68 @HasPermissionAllDecorator('hg.admin')
71 69 def index(self, format='html'):
72 70 """GET /admin/settings: All items in the collection"""
73 71 # url('admin_settings')
74 72
75 73 defaults = SettingsModel().get_app_settings()
76 74 defaults.update(self.get_hg_ui_settings())
77 75 return htmlfill.render(
78 76 render('admin/settings/settings.html'),
79 77 defaults=defaults,
80 78 encoding="UTF-8",
81 79 force_defaults=False
82 80 )
83 81
84 82 @HasPermissionAllDecorator('hg.admin')
85 83 def create(self):
86 84 """POST /admin/settings: Create a new item"""
87 85 # url('admin_settings')
88 86
89 87 @HasPermissionAllDecorator('hg.admin')
90 88 def new(self, format='html'):
91 89 """GET /admin/settings/new: Form to create a new item"""
92 90 # url('admin_new_setting')
93 91
94 92 @HasPermissionAllDecorator('hg.admin')
95 93 def update(self, setting_id):
96 94 """PUT /admin/settings/setting_id: Update an existing item"""
97 95 # Forms posted to this method should contain a hidden field:
98 96 # <input type="hidden" name="_method" value="PUT" />
99 97 # Or using helpers:
100 98 # h.form(url('admin_setting', setting_id=ID),
101 99 # method='put')
102 100 # url('admin_setting', setting_id=ID)
103 101 if setting_id == 'mapping':
104 102 rm_obsolete = request.POST.get('destroy', False)
105 103 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
106 104 initial = ScmModel().repo_scan()
107 105 log.debug('invalidating all repositories')
108 106 for repo_name in initial.keys():
109 107 invalidate_cache('get_repo_cached_%s' % repo_name)
110 108
111 109 added, removed = repo2db_mapper(initial, rm_obsolete)
112 110
113 111 h.flash(_('Repositories successfully'
114 112 ' rescanned added: %s,removed: %s') % (added, removed)
115 113 , category='success')
116 114
117 115 if setting_id == 'whoosh':
118 116 repo_location = self.get_hg_ui_settings()['paths_root_path']
119 117 full_index = request.POST.get('full_index', False)
120 118 run_task(tasks.whoosh_index, repo_location, full_index)
121 119
122 120 h.flash(_('Whoosh reindex task scheduled'), category='success')
123 121 if setting_id == 'global':
124 122
125 123 application_form = ApplicationSettingsForm()()
126 124 try:
127 125 form_result = application_form.to_python(dict(request.POST))
128 126 settings_model = SettingsModel()
129 127
130 128 try:
131 129 hgsettings1 = settings_model.get('title')
132 130 hgsettings1.app_settings_value = form_result['rhodecode_title']
133 131
134 132 hgsettings2 = settings_model.get('realm')
135 133 hgsettings2.app_settings_value = form_result['rhodecode_realm']
136 134
137 135 hgsettings3 = settings_model.get('ga_code')
138 136 hgsettings3.app_settings_value = form_result['rhodecode_ga_code']
139 137
140 138
141 139
142 140 self.sa.add(hgsettings1)
143 141 self.sa.add(hgsettings2)
144 142 self.sa.add(hgsettings3)
145 143 self.sa.commit()
146 144 set_rhodecode_config(config)
147 145 h.flash(_('Updated application settings'),
148 146 category='success')
149 147
150 148 except Exception:
151 149 log.error(traceback.format_exc())
152 150 h.flash(_('error occurred during updating application settings'),
153 151 category='error')
154 152
155 153 self.sa.rollback()
156 154
157 155
158 156 except formencode.Invalid, errors:
159 157 return htmlfill.render(
160 158 render('admin/settings/settings.html'),
161 159 defaults=errors.value,
162 160 errors=errors.error_dict or {},
163 161 prefix_error=False,
164 162 encoding="UTF-8")
165 163
166 164 if setting_id == 'mercurial':
167 165 application_form = ApplicationUiSettingsForm()()
168 166 try:
169 167 form_result = application_form.to_python(dict(request.POST))
170 168
171 169 try:
172 170
173 171 hgsettings1 = self.sa.query(RhodeCodeUi)\
174 172 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
175 173 hgsettings1.ui_value = form_result['web_push_ssl']
176 174
177 175 hgsettings2 = self.sa.query(RhodeCodeUi)\
178 176 .filter(RhodeCodeUi.ui_key == '/').one()
179 177 hgsettings2.ui_value = form_result['paths_root_path']
180 178
181 179
182 180 #HOOKS
183 181 hgsettings3 = self.sa.query(RhodeCodeUi)\
184 182 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
185 183 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
186 184
187 185 hgsettings4 = self.sa.query(RhodeCodeUi)\
188 186 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
189 187 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
190 188
191 189 hgsettings5 = self.sa.query(RhodeCodeUi)\
192 190 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
193 191 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
194 192
195 193 hgsettings6 = self.sa.query(RhodeCodeUi)\
196 194 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
197 195 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
198 196
199 197
200 198 self.sa.add(hgsettings1)
201 199 self.sa.add(hgsettings2)
202 200 self.sa.add(hgsettings3)
203 201 self.sa.add(hgsettings4)
204 202 self.sa.add(hgsettings5)
205 203 self.sa.add(hgsettings6)
206 204 self.sa.commit()
207 205
208 206 h.flash(_('Updated mercurial settings'),
209 207 category='success')
210 208
211 209 except:
212 210 log.error(traceback.format_exc())
213 211 h.flash(_('error occurred during updating application settings'),
214 212 category='error')
215 213
216 214 self.sa.rollback()
217 215
218 216
219 217 except formencode.Invalid, errors:
220 218 return htmlfill.render(
221 219 render('admin/settings/settings.html'),
222 220 defaults=errors.value,
223 221 errors=errors.error_dict or {},
224 222 prefix_error=False,
225 223 encoding="UTF-8")
226 224
227 225
228 226
229 227 return redirect(url('admin_settings'))
230 228
231 229 @HasPermissionAllDecorator('hg.admin')
232 230 def delete(self, setting_id):
233 231 """DELETE /admin/settings/setting_id: Delete an existing item"""
234 232 # Forms posted to this method should contain a hidden field:
235 233 # <input type="hidden" name="_method" value="DELETE" />
236 234 # Or using helpers:
237 235 # h.form(url('admin_setting', setting_id=ID),
238 236 # method='delete')
239 237 # url('admin_setting', setting_id=ID)
240 238
241 239 @HasPermissionAllDecorator('hg.admin')
242 240 def show(self, setting_id, format='html'):
243 241 """GET /admin/settings/setting_id: Show a specific item"""
244 242 # url('admin_setting', setting_id=ID)
245 243
246 244 @HasPermissionAllDecorator('hg.admin')
247 245 def edit(self, setting_id, format='html'):
248 246 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
249 247 # url('admin_edit_setting', setting_id=ID)
250 248
251 249 @NotAnonymous()
252 250 def my_account(self):
253 251 """
254 252 GET /_admin/my_account Displays info about my account
255 253 """
256 254 # url('admin_settings_my_account')
257 255
258 256 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
259 257 all_repos = [r.repo_name for r in self.sa.query(Repository)\
260 258 .filter(Repository.user_id == c.user.user_id)\
261 259 .order_by(func.lower(Repository.repo_name)).all()]
262 260 c.user_repos = ScmModel().get_repos(all_repos)
263 261
264 262 if c.user.username == 'default':
265 263 h.flash(_("You can't edit this user since it's"
266 264 " crucial for entire application"), category='warning')
267 265 return redirect(url('users'))
268 266
269 267 defaults = c.user.get_dict()
270 268 return htmlfill.render(
271 269 render('admin/users/user_edit_my_account.html'),
272 270 defaults=defaults,
273 271 encoding="UTF-8",
274 272 force_defaults=False
275 273 )
276 274
277 275 def my_account_update(self):
278 276 """PUT /_admin/my_account_update: Update an existing item"""
279 277 # Forms posted to this method should contain a hidden field:
280 278 # <input type="hidden" name="_method" value="PUT" />
281 279 # Or using helpers:
282 280 # h.form(url('admin_settings_my_account_update'),
283 281 # method='put')
284 282 # url('admin_settings_my_account_update', id=ID)
285 283 user_model = UserModel()
286 284 uid = self.rhodecode_user.user_id
287 285 _form = UserForm(edit=True, old_data={'user_id':uid,
288 286 'email':self.rhodecode_user.email})()
289 287 form_result = {}
290 288 try:
291 289 form_result = _form.to_python(dict(request.POST))
292 290 user_model.update_my_account(uid, form_result)
293 291 h.flash(_('Your account was updated successfully'),
294 292 category='success')
295 293
296 294 except formencode.Invalid, errors:
297 295 c.user = user_model.get(self.rhodecode_user.user_id, cache=False)
298 296 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
299 297 all_repos = self.sa.query(Repository)\
300 298 .filter(Repository.user_id == c.user.user_id)\
301 299 .order_by(func.lower(Repository.repo_name))\
302 300 .all()
303 301 c.user_repos = ScmModel().get_repos(all_repos)
304 302
305 303 return htmlfill.render(
306 304 render('admin/users/user_edit_my_account.html'),
307 305 defaults=errors.value,
308 306 errors=errors.error_dict or {},
309 307 prefix_error=False,
310 308 encoding="UTF-8")
311 309 except Exception:
312 310 log.error(traceback.format_exc())
313 311 h.flash(_('error occurred during update of user %s') \
314 312 % form_result.get('username'), category='error')
315 313
316 314 return redirect(url('my_account'))
317 315
318 316 @NotAnonymous()
319 317 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
320 318 def create_repository(self):
321 319 """GET /_admin/create_repository: Form to create a new item"""
322 320
323 321 c.repo_groups = [('', '')]
324 322 parents_link = lambda k:h.literal('&raquo;'.join(
325 323 map(lambda k:k.group_name,
326 324 k.parents + [k])
327 325 )
328 326 )
329 327
330 328 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
331 329 x in self.sa.query(Group).all()])
332 330 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
333 331
334 332 new_repo = request.GET.get('repo', '')
335 333 c.new_repo = repo_name_slug(new_repo)
336 334
337 335 return render('admin/repos/repo_add_create_repository.html')
338 336
339 337 def get_hg_ui_settings(self):
340 338 ret = self.sa.query(RhodeCodeUi).all()
341 339
342 340 if not ret:
343 341 raise Exception('Could not get application ui settings !')
344 342 settings = {}
345 343 for each in ret:
346 344 k = each.ui_key
347 345 v = each.ui_value
348 346 if k == '/':
349 347 k = 'root_path'
350 348
351 349 if k.find('.') != -1:
352 350 k = k.replace('.', '_')
353 351
354 352 if each.ui_section == 'hooks':
355 353 v = each.ui_active
356 354
357 355 settings[each.ui_section + '_' + k] = v
358 356
359 357 return settings
@@ -1,176 +1,174 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Users crud controller for pylons
7 7
8 8 :created_on: Apr 4, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 import traceback
30 28 import formencode
31 29
32 30 from formencode import htmlfill
33 31 from pylons import request, session, tmpl_context as c, url, config
34 32 from pylons.controllers.util import abort, redirect
35 33 from pylons.i18n.translation import _
36 34
37 35 from rhodecode.lib.exceptions import DefaultUserException, UserOwnsReposException
38 36 from rhodecode.lib import helpers as h
39 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
40 38 from rhodecode.lib.base import BaseController, render
41 39
42 40 from rhodecode.model.db import User
43 41 from rhodecode.model.forms import UserForm
44 42 from rhodecode.model.user import UserModel
45 43
46 44 log = logging.getLogger(__name__)
47 45
48 46 class UsersController(BaseController):
49 47 """REST Controller styled on the Atom Publishing Protocol"""
50 48 # To properly map this controller, ensure your config/routing.py
51 49 # file has a resource setup:
52 50 # map.resource('user', 'users')
53 51
54 52 @LoginRequired()
55 53 @HasPermissionAllDecorator('hg.admin')
56 54 def __before__(self):
57 55 c.admin_user = session.get('admin_user')
58 56 c.admin_username = session.get('admin_username')
59 57 super(UsersController, self).__before__()
60 58 c.available_permissions = config['available_permissions']
61 59
62 60 def index(self, format='html'):
63 61 """GET /users: All items in the collection"""
64 62 # url('users')
65 63
66 64 c.users_list = self.sa.query(User).all()
67 65 return render('admin/users/users.html')
68 66
69 67 def create(self):
70 68 """POST /users: Create a new item"""
71 69 # url('users')
72 70
73 71 user_model = UserModel()
74 72 login_form = UserForm()()
75 73 try:
76 74 form_result = login_form.to_python(dict(request.POST))
77 75 user_model.create(form_result)
78 76 h.flash(_('created user %s') % form_result['username'],
79 77 category='success')
80 78 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
81 79 except formencode.Invalid, errors:
82 80 return htmlfill.render(
83 81 render('admin/users/user_add.html'),
84 82 defaults=errors.value,
85 83 errors=errors.error_dict or {},
86 84 prefix_error=False,
87 85 encoding="UTF-8")
88 86 except Exception:
89 87 log.error(traceback.format_exc())
90 88 h.flash(_('error occurred during creation of user %s') \
91 89 % request.POST.get('username'), category='error')
92 90 return redirect(url('users'))
93 91
94 92 def new(self, format='html'):
95 93 """GET /users/new: Form to create a new item"""
96 94 # url('new_user')
97 95 return render('admin/users/user_add.html')
98 96
99 97 def update(self, id):
100 98 """PUT /users/id: Update an existing item"""
101 99 # Forms posted to this method should contain a hidden field:
102 100 # <input type="hidden" name="_method" value="PUT" />
103 101 # Or using helpers:
104 102 # h.form(url('user', id=ID),
105 103 # method='put')
106 104 # url('user', id=ID)
107 105 user_model = UserModel()
108 106 c.user = user_model.get(id)
109 107
110 108 _form = UserForm(edit=True, old_data={'user_id':id,
111 109 'email':c.user.email})()
112 110 form_result = {}
113 111 try:
114 112 form_result = _form.to_python(dict(request.POST))
115 113 user_model.update(id, form_result)
116 114 h.flash(_('User updated succesfully'), category='success')
117 115
118 116 except formencode.Invalid, errors:
119 117 return htmlfill.render(
120 118 render('admin/users/user_edit.html'),
121 119 defaults=errors.value,
122 120 errors=errors.error_dict or {},
123 121 prefix_error=False,
124 122 encoding="UTF-8")
125 123 except Exception:
126 124 log.error(traceback.format_exc())
127 125 h.flash(_('error occurred during update of user %s') \
128 126 % form_result.get('username'), category='error')
129 127
130 128 return redirect(url('users'))
131 129
132 130 def delete(self, id):
133 131 """DELETE /users/id: Delete an existing item"""
134 132 # Forms posted to this method should contain a hidden field:
135 133 # <input type="hidden" name="_method" value="DELETE" />
136 134 # Or using helpers:
137 135 # h.form(url('user', id=ID),
138 136 # method='delete')
139 137 # url('user', id=ID)
140 138 user_model = UserModel()
141 139 try:
142 140 user_model.delete(id)
143 141 h.flash(_('successfully deleted user'), category='success')
144 142 except (UserOwnsReposException, DefaultUserException), e:
145 143 h.flash(str(e), category='warning')
146 144 except Exception:
147 145 h.flash(_('An error occurred during deletion of user'),
148 146 category='error')
149 147 return redirect(url('users'))
150 148
151 149 def show(self, id, format='html'):
152 150 """GET /users/id: Show a specific item"""
153 151 # url('user', id=ID)
154 152
155 153
156 154 def edit(self, id, format='html'):
157 155 """GET /users/id/edit: Form to edit an existing item"""
158 156 # url('edit_user', id=ID)
159 157 user_model = UserModel()
160 158 c.user = user_model.get(id)
161 159 if not c.user:
162 160 return redirect(url('users'))
163 161 if c.user.username == 'default':
164 162 h.flash(_("You can't edit this user"), category='warning')
165 163 return redirect(url('users'))
166 164 c.user.permissions = {}
167 165 c.granted_permissions = user_model.fill_perms(c.user).permissions['global']
168 166
169 167 defaults = c.user.get_dict()
170 168
171 169 return htmlfill.render(
172 170 render('admin/users/user_edit.html'),
173 171 defaults=defaults,
174 172 encoding="UTF-8",
175 173 force_defaults=False
176 174 )
@@ -1,184 +1,182 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Users Groups crud controller for pylons
7 7
8 8 :created_on: Jan 25, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 import traceback
30 28 import formencode
31 29
32 30 from formencode import htmlfill
33 31 from pylons import request, session, tmpl_context as c, url, config
34 32 from pylons.controllers.util import abort, redirect
35 33 from pylons.i18n.translation import _
36 34
37 35 from rhodecode.lib.exceptions import DefaultUserException, UserOwnsReposException
38 36 from rhodecode.lib import helpers as h
39 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
40 38 from rhodecode.lib.base import BaseController, render
41 39
42 40 from rhodecode.model.db import User, UsersGroup
43 41 from rhodecode.model.forms import UserForm, UsersGroupForm
44 42 from rhodecode.model.user import UserModel
45 43 from rhodecode.model.users_group import UsersGroupModel
46 44
47 45 log = logging.getLogger(__name__)
48 46
49 47 class UsersGroupsController(BaseController):
50 48 """REST Controller styled on the Atom Publishing Protocol"""
51 49 # To properly map this controller, ensure your config/routing.py
52 50 # file has a resource setup:
53 51 # map.resource('users_group', 'users_groups')
54 52
55 53 @LoginRequired()
56 54 @HasPermissionAllDecorator('hg.admin')
57 55 def __before__(self):
58 56 c.admin_user = session.get('admin_user')
59 57 c.admin_username = session.get('admin_username')
60 58 super(UsersGroupsController, self).__before__()
61 59 c.available_permissions = config['available_permissions']
62 60
63 61 def index(self, format='html'):
64 62 """GET /users_groups: All items in the collection"""
65 63 # url('users_groups')
66 64 c.users_groups_list = self.sa.query(UsersGroup).all()
67 65 return render('admin/users_groups/users_groups.html')
68 66
69 67 def create(self):
70 68 """POST /users_groups: Create a new item"""
71 69 # url('users_groups')
72 70 users_group_model = UsersGroupModel()
73 71 users_group_form = UsersGroupForm()()
74 72 try:
75 73 form_result = users_group_form.to_python(dict(request.POST))
76 74 users_group_model.create(form_result)
77 75 h.flash(_('created users group %s') % form_result['users_group_name'],
78 76 category='success')
79 77 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
80 78 except formencode.Invalid, errors:
81 79 return htmlfill.render(
82 80 render('admin/users_groups/users_group_add.html'),
83 81 defaults=errors.value,
84 82 errors=errors.error_dict or {},
85 83 prefix_error=False,
86 84 encoding="UTF-8")
87 85 except Exception:
88 86 log.error(traceback.format_exc())
89 87 h.flash(_('error occurred during creation of users group %s') \
90 88 % request.POST.get('users_group_name'), category='error')
91 89
92 90 return redirect(url('users_groups'))
93 91
94 92 def new(self, format='html'):
95 93 """GET /users_groups/new: Form to create a new item"""
96 94 # url('new_users_group')
97 95 return render('admin/users_groups/users_group_add.html')
98 96
99 97 def update(self, id):
100 98 """PUT /users_groups/id: Update an existing item"""
101 99 # Forms posted to this method should contain a hidden field:
102 100 # <input type="hidden" name="_method" value="PUT" />
103 101 # Or using helpers:
104 102 # h.form(url('users_group', id=ID),
105 103 # method='put')
106 104 # url('users_group', id=ID)
107 105
108 106
109 107 users_group_model = UsersGroupModel()
110 108 c.users_group = users_group_model.get(id)
111 109 c.group_members = [(x.user_id, x.user.username) for x in
112 110 c.users_group.members]
113 111
114 112 c.available_members = [(x.user_id, x.username) for x in
115 113 self.sa.query(User).all()]
116 114 users_group_form = UsersGroupForm(edit=True,
117 115 old_data=c.users_group.get_dict(),
118 116 available_members=[str(x[0]) for x
119 117 in c.available_members])()
120 118
121 119 try:
122 120 form_result = users_group_form.to_python(request.POST)
123 121 users_group_model.update(id, form_result)
124 122 h.flash(_('updated users group %s') % form_result['users_group_name'],
125 123 category='success')
126 124 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
127 125 except formencode.Invalid, errors:
128 126 return htmlfill.render(
129 127 render('admin/users_groups/users_group_edit.html'),
130 128 defaults=errors.value,
131 129 errors=errors.error_dict or {},
132 130 prefix_error=False,
133 131 encoding="UTF-8")
134 132 except Exception:
135 133 log.error(traceback.format_exc())
136 134 h.flash(_('error occurred during update of users group %s') \
137 135 % request.POST.get('users_group_name'), category='error')
138 136
139 137 return redirect(url('users_groups'))
140 138
141 139
142 140
143 141 def delete(self, id):
144 142 """DELETE /users_groups/id: Delete an existing item"""
145 143 # Forms posted to this method should contain a hidden field:
146 144 # <input type="hidden" name="_method" value="DELETE" />
147 145 # Or using helpers:
148 146 # h.form(url('users_group', id=ID),
149 147 # method='delete')
150 148 # url('users_group', id=ID)
151 149 users_group_model = UsersGroupModel()
152 150 try:
153 151 users_group_model.delete(id)
154 152 h.flash(_('successfully deleted users group'), category='success')
155 153 except Exception:
156 154 h.flash(_('An error occurred during deletion of users group'),
157 155 category='error')
158 156 return redirect(url('users_groups'))
159 157
160 158 def show(self, id, format='html'):
161 159 """GET /users_groups/id: Show a specific item"""
162 160 # url('users_group', id=ID)
163 161
164 162 def edit(self, id, format='html'):
165 163 """GET /users_groups/id/edit: Form to edit an existing item"""
166 164 # url('edit_users_group', id=ID)
167 165
168 166 c.users_group = self.sa.query(UsersGroup).get(id)
169 167 if not c.users_group:
170 168 return redirect(url('users_groups'))
171 169
172 170 c.users_group.permissions = {}
173 171 c.group_members = [(x.user_id, x.user.username) for x in
174 172 c.users_group.members]
175 173 c.available_members = [(x.user_id, x.username) for x in
176 174 self.sa.query(User).all()]
177 175 defaults = c.users_group.get_dict()
178 176
179 177 return htmlfill.render(
180 178 render('admin/users_groups/users_group_edit.html'),
181 179 defaults=defaults,
182 180 encoding="UTF-8",
183 181 force_defaults=False
184 182 )
@@ -1,52 +1,50 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.branches
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 branches controller for rhodecode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27
30 28 from pylons import tmpl_context as c
31 29
32 30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 31 from rhodecode.lib.base import BaseRepoController, render
34 32 from rhodecode.lib.utils import OrderedDict
35 33
36 34 log = logging.getLogger(__name__)
37 35
38 36 class BranchesController(BaseRepoController):
39 37
40 38 @LoginRequired()
41 39 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 40 'repository.admin')
43 41 def __before__(self):
44 42 super(BranchesController, self).__before__()
45 43
46 44 def index(self):
47 45
48 46 c.repo_branches = OrderedDict()
49 47 for name, hash_ in c.rhodecode_repo.branches.items():
50 48 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash_)
51 49
52 50 return render('branches/branches.html')
@@ -1,105 +1,103 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changelog
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 changelog controller for rhodecode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27
30 28 try:
31 29 import json
32 30 except ImportError:
33 31 #python 2.5 compatibility
34 32 import simplejson as json
35 33
36 34 from mercurial.graphmod import colored, CHANGESET, revisions as graph_rev
37 35 from pylons import request, session, tmpl_context as c
38 36
39 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 38 from rhodecode.lib.base import BaseRepoController, render
41 39 from rhodecode.lib.helpers import RepoPage
42 40
43 41 log = logging.getLogger(__name__)
44 42
45 43 class ChangelogController(BaseRepoController):
46 44
47 45 @LoginRequired()
48 46 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
49 47 'repository.admin')
50 48 def __before__(self):
51 49 super(ChangelogController, self).__before__()
52 50 c.affected_files_cut_off = 60
53 51
54 52 def index(self):
55 53 limit = 100
56 54 default = 20
57 55 if request.params.get('size'):
58 56 try:
59 57 int_size = int(request.params.get('size'))
60 58 except ValueError:
61 59 int_size = default
62 60 int_size = int_size if int_size <= limit else limit
63 61 c.size = int_size
64 62 session['changelog_size'] = c.size
65 63 session.save()
66 64 else:
67 65 c.size = int(session.get('changelog_size', default))
68 66
69 67 p = int(request.params.get('page', 1))
70 68 branch_name = request.params.get('branch', None)
71 69 c.total_cs = len(c.rhodecode_repo)
72 70 c.pagination = RepoPage(c.rhodecode_repo, page=p, item_count=c.total_cs,
73 71 items_per_page=c.size, branch_name=branch_name)
74 72
75 73 self._graph(c.rhodecode_repo, c.total_cs, c.size, p)
76 74
77 75 return render('changelog/changelog.html')
78 76
79 77
80 78 def _graph(self, repo, repo_size, size, p):
81 79 """
82 80 Generates a DAG graph for mercurial
83 81
84 82 :param repo: repo instance
85 83 :param size: number of commits to show
86 84 :param p: page number
87 85 """
88 86 if not repo.revisions or repo.alias == 'git':
89 87 c.jsdata = json.dumps([])
90 88 return
91 89
92 90 revcount = min(repo_size, size)
93 91 offset = 1 if p == 1 else ((p - 1) * revcount + 1)
94 92 rev_start = repo.revisions.index(repo.revisions[(-1 * offset)])
95 93 rev_end = max(0, rev_start - revcount)
96 94
97 95 dag = graph_rev(repo._repo, rev_start, rev_end)
98 96 c.dag = tree = list(colored(dag))
99 97 data = []
100 98 for (id, type, ctx, vtx, edges) in tree:
101 99 if type != CHANGESET:
102 100 continue
103 101 data.append(('', vtx, edges))
104 102
105 103 c.jsdata = json.dumps(data)
@@ -1,219 +1,217 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changeset
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 changeset controller for pylons showoing changes beetween
7 7 revisions
8 8
9 9 :created_on: Apr 25, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 26 import logging
29 27 import traceback
30 28
31 29 from pylons import tmpl_context as c, url, request, response
32 30 from pylons.i18n.translation import _
33 31 from pylons.controllers.util import redirect
34 32
35 33 import rhodecode.lib.helpers as h
36 34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 35 from rhodecode.lib.base import BaseRepoController, render
38 36 from rhodecode.lib.utils import EmptyChangeset
39 37
40 38 from vcs.exceptions import RepositoryError, ChangesetError, \
41 39 ChangesetDoesNotExistError
42 40 from vcs.nodes import FileNode
43 41 from vcs.utils import diffs as differ
44 42 from vcs.utils.ordered_dict import OrderedDict
45 43
46 44 log = logging.getLogger(__name__)
47 45
48 46 class ChangesetController(BaseRepoController):
49 47
50 48 @LoginRequired()
51 49 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
52 50 'repository.admin')
53 51 def __before__(self):
54 52 super(ChangesetController, self).__before__()
55 53 c.affected_files_cut_off = 60
56 54
57 55 def index(self, revision):
58 56
59 57 def wrap_to_table(str):
60 58
61 59 return '''<table class="code-difftable">
62 60 <tr class="line">
63 61 <td class="lineno new"></td>
64 62 <td class="code"><pre>%s</pre></td>
65 63 </tr>
66 64 </table>''' % str
67 65
68 66 #get ranges of revisions if preset
69 67 rev_range = revision.split('...')[:2]
70 68
71 69 try:
72 70 if len(rev_range) == 2:
73 71 rev_start = rev_range[0]
74 72 rev_end = rev_range[1]
75 73 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
76 74 end=rev_end)
77 75 else:
78 76 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
79 77
80 78 c.cs_ranges = list(rev_ranges)
81 79
82 80 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
83 81 log.error(traceback.format_exc())
84 82 h.flash(str(e), category='warning')
85 83 return redirect(url('home'))
86 84
87 85 c.changes = OrderedDict()
88 86 c.sum_added = 0
89 87 c.sum_removed = 0
90 88 c.cut_off = False
91 89
92 90 for changeset in c.cs_ranges:
93 91 c.changes[changeset.raw_id] = []
94 92 try:
95 93 changeset_parent = changeset.parents[0]
96 94 except IndexError:
97 95 changeset_parent = None
98 96
99 97
100 98 #==================================================================
101 99 # ADDED FILES
102 100 #==================================================================
103 101 for node in changeset.added:
104 102 filenode_old = FileNode(node.path, '', EmptyChangeset())
105 103 if filenode_old.is_binary or node.is_binary:
106 104 diff = wrap_to_table(_('binary file'))
107 105 else:
108 106 c.sum_added += node.size
109 107 if c.sum_added < self.cut_off_limit:
110 108 f_gitdiff = differ.get_gitdiff(filenode_old, node)
111 109 diff = differ.DiffProcessor(f_gitdiff, format='gitdiff').as_html()
112 110
113 111 else:
114 112 diff = wrap_to_table(_('Changeset is to big and was cut'
115 113 ' off, see raw changeset instead'))
116 114 c.cut_off = True
117 115 break
118 116
119 117 cs1 = None
120 118 cs2 = node.last_changeset.raw_id
121 119 c.changes[changeset.raw_id].append(('added', node, diff, cs1, cs2))
122 120
123 121 #==================================================================
124 122 # CHANGED FILES
125 123 #==================================================================
126 124 if not c.cut_off:
127 125 for node in changeset.changed:
128 126 try:
129 127 filenode_old = changeset_parent.get_node(node.path)
130 128 except ChangesetError:
131 129 filenode_old = FileNode(node.path, '', EmptyChangeset())
132 130
133 131 if filenode_old.is_binary or node.is_binary:
134 132 diff = wrap_to_table(_('binary file'))
135 133 else:
136 134
137 135 if c.sum_removed < self.cut_off_limit:
138 136 f_gitdiff = differ.get_gitdiff(filenode_old, node)
139 137 diff = differ.DiffProcessor(f_gitdiff, format='gitdiff').as_html()
140 138 if diff:
141 139 c.sum_removed += len(diff)
142 140 else:
143 141 diff = wrap_to_table(_('Changeset is to big and was cut'
144 142 ' off, see raw changeset instead'))
145 143 c.cut_off = True
146 144 break
147 145
148 146 cs1 = filenode_old.last_changeset.raw_id
149 147 cs2 = node.last_changeset.raw_id
150 148 c.changes[changeset.raw_id].append(('changed', node, diff, cs1, cs2))
151 149
152 150 #==================================================================
153 151 # REMOVED FILES
154 152 #==================================================================
155 153 if not c.cut_off:
156 154 for node in changeset.removed:
157 155 c.changes[changeset.raw_id].append(('removed', node, None, None, None))
158 156
159 157 if len(c.cs_ranges) == 1:
160 158 c.changeset = c.cs_ranges[0]
161 159 c.changes = c.changes[c.changeset.raw_id]
162 160
163 161 return render('changeset/changeset.html')
164 162 else:
165 163 return render('changeset/changeset_range.html')
166 164
167 165 def raw_changeset(self, revision):
168 166
169 167 method = request.GET.get('diff', 'show')
170 168 try:
171 169 c.scm_type = c.rhodecode_repo.alias
172 170 c.changeset = c.rhodecode_repo.get_changeset(revision)
173 171 except RepositoryError:
174 172 log.error(traceback.format_exc())
175 173 return redirect(url('home'))
176 174 else:
177 175 try:
178 176 c.changeset_parent = c.changeset.parents[0]
179 177 except IndexError:
180 178 c.changeset_parent = None
181 179 c.changes = []
182 180
183 181 for node in c.changeset.added:
184 182 filenode_old = FileNode(node.path, '')
185 183 if filenode_old.is_binary or node.is_binary:
186 184 diff = _('binary file') + '\n'
187 185 else:
188 186 f_gitdiff = differ.get_gitdiff(filenode_old, node)
189 187 diff = differ.DiffProcessor(f_gitdiff, format='gitdiff').raw_diff()
190 188
191 189 cs1 = None
192 190 cs2 = node.last_changeset.raw_id
193 191 c.changes.append(('added', node, diff, cs1, cs2))
194 192
195 193 for node in c.changeset.changed:
196 194 filenode_old = c.changeset_parent.get_node(node.path)
197 195 if filenode_old.is_binary or node.is_binary:
198 196 diff = _('binary file')
199 197 else:
200 198 f_gitdiff = differ.get_gitdiff(filenode_old, node)
201 199 diff = differ.DiffProcessor(f_gitdiff, format='gitdiff').raw_diff()
202 200
203 201 cs1 = filenode_old.last_changeset.raw_id
204 202 cs2 = node.last_changeset.raw_id
205 203 c.changes.append(('changed', node, diff, cs1, cs2))
206 204
207 205 response.content_type = 'text/plain'
208 206
209 207 if method == 'download':
210 208 response.content_disposition = 'attachment; filename=%s.patch' % revision
211 209
212 210 parent = True if len(c.changeset.parents) > 0 else False
213 211 c.parent_tmpl = 'Parent %s' % c.changeset.parents[0].raw_id if parent else ''
214 212
215 213 c.diffs = ''
216 214 for x in c.changes:
217 215 c.diffs += x[2]
218 216
219 217 return render('changeset/raw_changeset.html')
@@ -1,109 +1,107 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.error
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode error controller
7 7
8 8 :created_on: Dec 8, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25 import os
28 26 import cgi
29 27 import logging
30 28 import paste.fileapp
31 29
32 30 from pylons import tmpl_context as c, request, config
33 31 from pylons.i18n.translation import _
34 32 from pylons.middleware import media_path
35 33
36 34 from rhodecode.lib.base import BaseController, render
37 35
38 36 log = logging.getLogger(__name__)
39 37
40 38 class ErrorController(BaseController):
41 39 """Generates error documents as and when they are required.
42 40
43 41 The ErrorDocuments middleware forwards to ErrorController when error
44 42 related status codes are returned from the application.
45 43
46 44 This behavior can be altered by changing the parameters to the
47 45 ErrorDocuments middleware in your config/middleware.py file.
48 46 """
49 47
50 48 def __before__(self):
51 49 pass#disable all base actions since we don't need them here
52 50
53 51 def document(self):
54 52 resp = request.environ.get('pylons.original_response')
55 53 c.rhodecode_name = config.get('rhodecode_title')
56 54
57 55 log.debug('### %s ###', resp.status)
58 56
59 57 e = request.environ
60 58 c.serv_p = r'%(protocol)s://%(host)s/' % {
61 59 'protocol': e.get('wsgi.url_scheme'),
62 60 'host':e.get('HTTP_HOST'),
63 61 }
64 62
65 63
66 64 c.error_message = cgi.escape(request.GET.get('code', str(resp.status)))
67 65 c.error_explanation = self.get_error_explanation(resp.status_int)
68 66
69 67 #redirect to when error with given seconds
70 68 c.redirect_time = 0
71 69 c.redirect_module = _('Home page')# name to what your going to be redirected
72 70 c.url_redirect = "/"
73 71
74 72 return render('/errors/error_document.html')
75 73
76 74
77 75 def img(self, id):
78 76 """Serve Pylons' stock images"""
79 77 return self._serve_file(os.path.join(media_path, 'img', id))
80 78
81 79 def style(self, id):
82 80 """Serve Pylons' stock stylesheets"""
83 81 return self._serve_file(os.path.join(media_path, 'style', id))
84 82
85 83 def _serve_file(self, path):
86 84 """Call Paste's FileApp (a WSGI application) to serve the file
87 85 at the specified path
88 86 """
89 87 fapp = paste.fileapp.FileApp(path)
90 88 return fapp(request.environ, self.start_response)
91 89
92 90 def get_error_explanation(self, code):
93 91 ''' get the error explanations of int codes
94 92 [400, 401, 403, 404, 500]'''
95 93 try:
96 94 code = int(code)
97 95 except:
98 96 code = 500
99 97
100 98 if code == 400:
101 99 return _('The request could not be understood by the server due to malformed syntax.')
102 100 if code == 401:
103 101 return _('Unauthorized access to resource')
104 102 if code == 403:
105 103 return _("You don't have permission to view this page")
106 104 if code == 404:
107 105 return _('The resource could not be found')
108 106 if code == 500:
109 107 return _('The server encountered an unexpected condition which prevented it from fulfilling the request.')
@@ -1,115 +1,113 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.feed
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Feed controller for rhodecode
7 7
8 8 :created_on: Apr 23, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27
30 28 from pylons import url, response, tmpl_context as c
31 29 from pylons.i18n.translation import _
32 30
33 31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 32 from rhodecode.lib.base import BaseRepoController
35 33
36 34 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
37 35
38 36 log = logging.getLogger(__name__)
39 37
40 38 class FeedController(BaseRepoController):
41 39
42 40 @LoginRequired(api_access=True)
43 41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
44 42 'repository.admin')
45 43 def __before__(self):
46 44 super(FeedController, self).__before__()
47 45 #common values for feeds
48 46 self.description = _('Changes on %s repository')
49 47 self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
50 48 self.language = 'en-us'
51 49 self.ttl = "5"
52 50 self.feed_nr = 10
53 51
54 52 def __changes(self, cs):
55 53 changes = ''
56 54
57 55 a = [n.path for n in cs.added]
58 56 if a:
59 57 changes += '\nA ' + '\nA '.join(a)
60 58
61 59 m = [n.path for n in cs.changed]
62 60 if m:
63 61 changes += '\nM ' + '\nM '.join(m)
64 62
65 63 d = [n.path for n in cs.removed]
66 64 if d:
67 65 changes += '\nD ' + '\nD '.join(d)
68 66
69 67 changes += '</pre>'
70 68
71 69 return changes
72 70
73 71 def atom(self, repo_name):
74 72 """Produce an atom-1.0 feed via feedgenerator module"""
75 73 feed = Atom1Feed(title=self.title % repo_name,
76 74 link=url('summary_home', repo_name=repo_name, qualified=True),
77 75 description=self.description % repo_name,
78 76 language=self.language,
79 77 ttl=self.ttl)
80 78
81 79 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
82 80 desc = '%s - %s<br/><pre>' % (cs.author, cs.date)
83 81 desc += self.__changes(cs)
84 82
85 83 feed.add_item(title=cs.message,
86 84 link=url('changeset_home', repo_name=repo_name,
87 85 revision=cs.raw_id, qualified=True),
88 86 author_name=cs.author,
89 87 description=desc)
90 88
91 89 response.content_type = feed.mime_type
92 90 return feed.writeString('utf-8')
93 91
94 92
95 93 def rss(self, repo_name):
96 94 """Produce an rss2 feed via feedgenerator module"""
97 95 feed = Rss201rev2Feed(title=self.title % repo_name,
98 96 link=url('summary_home', repo_name=repo_name, qualified=True),
99 97 description=self.description % repo_name,
100 98 language=self.language,
101 99 ttl=self.ttl)
102 100
103 101 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
104 102 desc = '%s - %s<br/><pre>' % (cs.author, cs.date)
105 103 desc += self.__changes(cs)
106 104
107 105 feed.add_item(title=cs.message,
108 106 link=url('changeset_home', repo_name=repo_name,
109 107 revision=cs.raw_id, qualified=True),
110 108 author_name=cs.author,
111 109 description=desc,
112 110 )
113 111
114 112 response.content_type = feed.mime_type
115 113 return feed.writeString('utf-8')
@@ -1,303 +1,301 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import os
29 27 import logging
30 28 import rhodecode.lib.helpers as h
31 29
32 30 from pylons import request, response, session, tmpl_context as c, url
33 31 from pylons.i18n.translation import _
34 32 from pylons.controllers.util import redirect
35 33
36 34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 35 from rhodecode.lib.base import BaseRepoController, render
38 36 from rhodecode.lib.utils import EmptyChangeset
39 37 from rhodecode.model.repo import RepoModel
40 38
41 39 from vcs.backends import ARCHIVE_SPECS
42 40 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
43 41 EmptyRepositoryError, ImproperArchiveTypeError, VCSError
44 42 from vcs.nodes import FileNode, NodeKind
45 43 from vcs.utils import diffs as differ
46 44
47 45 log = logging.getLogger(__name__)
48 46
49 47
50 48 class FilesController(BaseRepoController):
51 49
52 50 @LoginRequired()
53 51 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
54 52 'repository.admin')
55 53 def __before__(self):
56 54 super(FilesController, self).__before__()
57 55 c.cut_off_limit = self.cut_off_limit
58 56
59 57 def __get_cs_or_redirect(self, rev, repo_name):
60 58 """
61 59 Safe way to get changeset if error occur it redirects to tip with
62 60 proper message
63 61
64 62 :param rev: revision to fetch
65 63 :param repo_name: repo name to redirect after
66 64 """
67 65
68 66 try:
69 67 return c.rhodecode_repo.get_changeset(rev)
70 68 except EmptyRepositoryError, e:
71 69 h.flash(_('There are no files yet'), category='warning')
72 70 redirect(h.url('summary_home', repo_name=repo_name))
73 71
74 72 except RepositoryError, e:
75 73 h.flash(str(e), category='warning')
76 74 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
77 75
78 76
79 77 def __get_filenode_or_redirect(self, repo_name, cs, path):
80 78 """
81 79 Returns file_node, if error occurs or given path is directory,
82 80 it'll redirect to top level path
83 81
84 82 :param repo_name: repo_name
85 83 :param cs: given changeset
86 84 :param path: path to lookup
87 85 """
88 86
89 87
90 88 try:
91 89 file_node = cs.get_node(path)
92 90 if file_node.is_dir():
93 91 raise RepositoryError('given path is a directory')
94 92 except RepositoryError, e:
95 93 h.flash(str(e), category='warning')
96 94 redirect(h.url('files_home', repo_name=repo_name,
97 95 revision=cs.raw_id))
98 96
99 97 return file_node
100 98
101 99 def index(self, repo_name, revision, f_path):
102 100 #reditect to given revision from form if given
103 101 post_revision = request.POST.get('at_rev', None)
104 102 if post_revision:
105 103 cs = self.__get_cs_or_redirect(revision, repo_name)
106 104 redirect(url('files_home', repo_name=c.repo_name,
107 105 revision=cs.raw_id, f_path=f_path))
108 106
109 107
110 108 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
111 109 c.branch = request.GET.get('branch', None)
112 110 c.f_path = f_path
113 111
114 112 cur_rev = c.changeset.revision
115 113
116 114 #prev link
117 115 try:
118 116 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
119 117 c.url_prev = url('files_home', repo_name=c.repo_name,
120 118 revision=prev_rev.raw_id, f_path=f_path)
121 119 if c.branch:
122 120 c.url_prev += '?branch=%s' % c.branch
123 121 except (ChangesetDoesNotExistError, VCSError):
124 122 c.url_prev = '#'
125 123
126 124 #next link
127 125 try:
128 126 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
129 127 c.url_next = url('files_home', repo_name=c.repo_name,
130 128 revision=next_rev.raw_id, f_path=f_path)
131 129 if c.branch:
132 130 c.url_next += '?branch=%s' % c.branch
133 131 except (ChangesetDoesNotExistError, VCSError):
134 132 c.url_next = '#'
135 133
136 134 #files or dirs
137 135 try:
138 136 c.files_list = c.changeset.get_node(f_path)
139 137
140 138 if c.files_list.is_file():
141 139 c.file_history = self._get_node_history(c.changeset, f_path)
142 140 else:
143 141 c.file_history = []
144 142 except RepositoryError, e:
145 143 h.flash(str(e), category='warning')
146 144 redirect(h.url('files_home', repo_name=repo_name,
147 145 revision=revision))
148 146
149 147
150 148 return render('files/files.html')
151 149
152 150 def rawfile(self, repo_name, revision, f_path):
153 151 cs = self.__get_cs_or_redirect(revision, repo_name)
154 152 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
155 153
156 154 response.content_disposition = 'attachment; filename=%s' % \
157 155 f_path.split(os.sep)[-1].encode('utf8', 'replace')
158 156
159 157 response.content_type = file_node.mimetype
160 158 return file_node.content
161 159
162 160 def raw(self, repo_name, revision, f_path):
163 161 cs = self.__get_cs_or_redirect(revision, repo_name)
164 162 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
165 163
166 164 response.content_type = 'text/plain'
167 165 return file_node.content
168 166
169 167 def annotate(self, repo_name, revision, f_path):
170 168 c.cs = self.__get_cs_or_redirect(revision, repo_name)
171 169 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
172 170
173 171 c.file_history = self._get_node_history(c.cs, f_path)
174 172 c.f_path = f_path
175 173 return render('files/files_annotate.html')
176 174
177 175 def archivefile(self, repo_name, fname):
178 176
179 177 fileformat = None
180 178 revision = None
181 179 ext = None
182 180
183 181 for a_type, ext_data in ARCHIVE_SPECS.items():
184 182 archive_spec = fname.split(ext_data[1])
185 183 if len(archive_spec) == 2 and archive_spec[1] == '':
186 184 fileformat = a_type or ext_data[1]
187 185 revision = archive_spec[0]
188 186 ext = ext_data[1]
189 187
190 188 try:
191 189 dbrepo = RepoModel().get_by_repo_name(repo_name)
192 190 if dbrepo.enable_downloads is False:
193 191 return _('downloads disabled')
194 192
195 193 cs = c.rhodecode_repo.get_changeset(revision)
196 194 content_type = ARCHIVE_SPECS[fileformat][0]
197 195 except ChangesetDoesNotExistError:
198 196 return _('Unknown revision %s') % revision
199 197 except EmptyRepositoryError:
200 198 return _('Empty repository')
201 199 except (ImproperArchiveTypeError, KeyError):
202 200 return _('Unknown archive type')
203 201
204 202 response.content_type = content_type
205 203 response.content_disposition = 'attachment; filename=%s-%s%s' \
206 204 % (repo_name, revision, ext)
207 205
208 206 return cs.get_chunked_archive(stream=None, kind=fileformat)
209 207
210 208
211 209 def diff(self, repo_name, f_path):
212 210 diff1 = request.GET.get('diff1')
213 211 diff2 = request.GET.get('diff2')
214 212 c.action = request.GET.get('diff')
215 213 c.no_changes = diff1 == diff2
216 214 c.f_path = f_path
217 215
218 216 try:
219 217 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
220 218 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
221 219 node1 = c.changeset_1.get_node(f_path)
222 220 else:
223 221 c.changeset_1 = EmptyChangeset()
224 222 node1 = FileNode('.', '', changeset=c.changeset_1)
225 223
226 224 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
227 225 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
228 226 node2 = c.changeset_2.get_node(f_path)
229 227 else:
230 228 c.changeset_2 = EmptyChangeset()
231 229 node2 = FileNode('.', '', changeset=c.changeset_2)
232 230 except RepositoryError:
233 231 return redirect(url('files_home',
234 232 repo_name=c.repo_name, f_path=f_path))
235 233
236 234
237 235 if c.action == 'download':
238 236 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
239 237 format='gitdiff')
240 238
241 239 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
242 240 response.content_type = 'text/plain'
243 241 response.content_disposition = 'attachment; filename=%s' \
244 242 % diff_name
245 243 return diff.raw_diff()
246 244
247 245 elif c.action == 'raw':
248 246 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
249 247 format='gitdiff')
250 248 response.content_type = 'text/plain'
251 249 return diff.raw_diff()
252 250
253 251 elif c.action == 'diff':
254 252
255 253 if node1.is_binary or node2.is_binary:
256 254 c.cur_diff = _('Binary file')
257 255 elif node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
258 256 c.cur_diff = _('Diff is too big to display')
259 257 else:
260 258 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
261 259 format='gitdiff')
262 260 c.cur_diff = diff.as_html()
263 261 else:
264 262
265 263 #default option
266 264 if node1.is_binary or node2.is_binary:
267 265 c.cur_diff = _('Binary file')
268 266 elif node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
269 267 c.cur_diff = _('Diff is too big to display')
270 268 else:
271 269 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
272 270 format='gitdiff')
273 271 c.cur_diff = diff.as_html()
274 272
275 273 if not c.cur_diff:
276 274 c.no_changes = True
277 275 return render('files/file_diff.html')
278 276
279 277 def _get_node_history(self, cs, f_path):
280 278 changesets = cs.get_file_history(f_path)
281 279 hist_l = []
282 280
283 281 changesets_group = ([], _("Changesets"))
284 282 branches_group = ([], _("Branches"))
285 283 tags_group = ([], _("Tags"))
286 284
287 285 for chs in changesets:
288 286 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
289 287 changesets_group[0].append((chs.raw_id, n_desc,))
290 288
291 289 hist_l.append(changesets_group)
292 290
293 291 for name, chs in c.rhodecode_repo.branches.items():
294 292 #chs = chs.split(':')[-1]
295 293 branches_group[0].append((chs, name),)
296 294 hist_l.append(branches_group)
297 295
298 296 for name, chs in c.rhodecode_repo.tags.items():
299 297 #chs = chs.split(':')[-1]
300 298 tags_group[0].append((chs, name),)
301 299 hist_l.append(tags_group)
302 300
303 301 return hist_l
@@ -1,76 +1,74 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.home
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Home controller for Rhodecode
7 7
8 8 :created_on: Feb 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 from operator import itemgetter
30 28
31 29 from pylons import tmpl_context as c, request
32 30 from paste.httpexceptions import HTTPBadRequest
33 31
34 32 from rhodecode.lib.auth import LoginRequired
35 33 from rhodecode.lib.base import BaseController, render
36 34
37 35
38 36 log = logging.getLogger(__name__)
39 37
40 38 class HomeController(BaseController):
41 39
42 40 @LoginRequired()
43 41 def __before__(self):
44 42 super(HomeController, self).__before__()
45 43
46 44 def index(self):
47 45 sortables = ['name', 'description', 'last_change', 'tip', 'owner']
48 46 current_sort = request.GET.get('sort', 'name')
49 47 current_sort_slug = current_sort.replace('-', '')
50 48
51 49 if current_sort_slug not in sortables:
52 50 c.sort_by = 'name'
53 51 current_sort_slug = c.sort_by
54 52 else:
55 53 c.sort_by = current_sort
56 54 c.sort_slug = current_sort_slug
57 55
58 56 sort_key = current_sort_slug + '_sort'
59 57
60 58 if c.sort_by.startswith('-'):
61 59 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
62 60 reverse=True)
63 61 else:
64 62 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
65 63 reverse=False)
66 64
67 65 c.repo_cnt = len(c.repos_list)
68 66 return render('/index.html')
69 67
70 68 def repo_switcher(self):
71 69 if request.is_xhr:
72 70 c.repos_list = sorted(c.cached_repo_list,
73 71 key=itemgetter('name_sort'), reverse=False)
74 72 return render('/repo_switcher_list.html')
75 73 else:
76 74 return HTTPBadRequest()
@@ -1,240 +1,238 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.journal
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Journal controller for pylons
7 7
8 8 :created_on: Nov 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25 import logging
28 26
29 27 from sqlalchemy import or_
30 28 from sqlalchemy.orm import joinedload, make_transient
31 29 from webhelpers.paginate import Page
32 30 from itertools import groupby
33 31
34 32 from paste.httpexceptions import HTTPBadRequest
35 33 from pylons import request, tmpl_context as c, response, url
36 34 from pylons.i18n.translation import _
37 35 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
38 36
39 37 import rhodecode.lib.helpers as h
40 38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
41 39 from rhodecode.lib.base import BaseController, render
42 40 from rhodecode.model.db import UserLog, UserFollowing
43 41
44 42 log = logging.getLogger(__name__)
45 43
46 44 class JournalController(BaseController):
47 45
48 46
49 47
50 48 def __before__(self):
51 49 super(JournalController, self).__before__()
52 50 self.rhodecode_user = self.rhodecode_user
53 51 self.title = _('%s public journal %s feed') % (c.rhodecode_name, '%s')
54 52 self.language = 'en-us'
55 53 self.ttl = "5"
56 54 self.feed_nr = 20
57 55
58 56 @LoginRequired()
59 57 @NotAnonymous()
60 58 def index(self):
61 59 # Return a rendered template
62 60 p = int(request.params.get('page', 1))
63 61
64 62 c.following = self.sa.query(UserFollowing)\
65 63 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
66 64 .options(joinedload(UserFollowing.follows_repository))\
67 65 .all()
68 66
69 67 journal = self._get_journal_data(c.following)
70 68
71 69 c.journal_pager = Page(journal, page=p, items_per_page=20)
72 70
73 71 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
74 72
75 73 c.journal_data = render('journal/journal_data.html')
76 74 if request.params.get('partial'):
77 75 return c.journal_data
78 76 return render('journal/journal.html')
79 77
80 78
81 79 def _get_daily_aggregate(self, journal):
82 80 groups = []
83 81 for k, g in groupby(journal, lambda x:x.action_as_day):
84 82 user_group = []
85 83 for k2, g2 in groupby(list(g), lambda x:x.user.email):
86 84 l = list(g2)
87 85 user_group.append((l[0].user, l))
88 86
89 87 groups.append((k, user_group,))
90 88
91 89 return groups
92 90
93 91
94 92 def _get_journal_data(self, following_repos):
95 93 repo_ids = [x.follows_repository.repo_id for x in following_repos
96 94 if x.follows_repository is not None]
97 95 user_ids = [x.follows_user.user_id for x in following_repos
98 96 if x.follows_user is not None]
99 97
100 98 filtering_criterion = None
101 99
102 100 if repo_ids and user_ids:
103 101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
104 102 UserLog.user_id.in_(user_ids))
105 103 if repo_ids and not user_ids:
106 104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
107 105 if not repo_ids and user_ids:
108 106 filtering_criterion = UserLog.user_id.in_(user_ids)
109 107 if filtering_criterion is not None:
110 108 journal = self.sa.query(UserLog)\
111 109 .options(joinedload(UserLog.user))\
112 110 .options(joinedload(UserLog.repository))\
113 111 .filter(filtering_criterion)\
114 112 .order_by(UserLog.action_date.desc())
115 113 else:
116 114 journal = []
117 115
118 116
119 117 return journal
120 118
121 119 @LoginRequired()
122 120 @NotAnonymous()
123 121 def toggle_following(self):
124 122 cur_token = request.POST.get('auth_token')
125 123 token = h.get_token()
126 124 if cur_token == token:
127 125
128 126 user_id = request.POST.get('follows_user_id')
129 127 if user_id:
130 128 try:
131 129 self.scm_model.toggle_following_user(user_id,
132 130 self.rhodecode_user.user_id)
133 131 return 'ok'
134 132 except:
135 133 raise HTTPBadRequest()
136 134
137 135 repo_id = request.POST.get('follows_repo_id')
138 136 if repo_id:
139 137 try:
140 138 self.scm_model.toggle_following_repo(repo_id,
141 139 self.rhodecode_user.user_id)
142 140 return 'ok'
143 141 except:
144 142 raise HTTPBadRequest()
145 143
146 144
147 145 log.debug('token mismatch %s vs %s', cur_token, token)
148 146 raise HTTPBadRequest()
149 147
150 148
151 149
152 150 @LoginRequired()
153 151 def public_journal(self):
154 152 # Return a rendered template
155 153 p = int(request.params.get('page', 1))
156 154
157 155 c.following = self.sa.query(UserFollowing)\
158 156 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
159 157 .options(joinedload(UserFollowing.follows_repository))\
160 158 .all()
161 159
162 160 journal = self._get_journal_data(c.following)
163 161
164 162 c.journal_pager = Page(journal, page=p, items_per_page=20)
165 163
166 164 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
167 165
168 166 c.journal_data = render('journal/journal_data.html')
169 167 if request.params.get('partial'):
170 168 return c.journal_data
171 169 return render('journal/public_journal.html')
172 170
173 171
174 172 @LoginRequired(api_access=True)
175 173 def public_journal_atom(self):
176 174 """
177 175 Produce an atom-1.0 feed via feedgenerator module
178 176 """
179 177 c.following = self.sa.query(UserFollowing)\
180 178 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
181 179 .options(joinedload(UserFollowing.follows_repository))\
182 180 .all()
183 181
184 182 journal = self._get_journal_data(c.following)
185 183
186 184 feed = Atom1Feed(title=self.title % 'atom',
187 185 link=url('public_journal_atom', qualified=True),
188 186 description=_('Public journal'),
189 187 language=self.language,
190 188 ttl=self.ttl)
191 189
192 190 for entry in journal[:self.feed_nr]:
193 191 #tmpl = h.action_parser(entry)[0]
194 192 action, action_extra = h.action_parser(entry, feed=True)
195 193 title = "%s - %s %s" % (entry.user.short_contact, action,
196 194 entry.repository.repo_name)
197 195 desc = action_extra()
198 196 feed.add_item(title=title,
199 197 pubdate=entry.action_date,
200 198 link=url('', qualified=True),
201 199 author_email=entry.user.email,
202 200 author_name=entry.user.full_contact,
203 201 description=desc)
204 202
205 203 response.content_type = feed.mime_type
206 204 return feed.writeString('utf-8')
207 205
208 206 @LoginRequired(api_access=True)
209 207 def public_journal_rss(self):
210 208 """
211 209 Produce an rss2 feed via feedgenerator module
212 210 """
213 211 c.following = self.sa.query(UserFollowing)\
214 212 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
215 213 .options(joinedload(UserFollowing.follows_repository))\
216 214 .all()
217 215
218 216 journal = self._get_journal_data(c.following)
219 217
220 218 feed = Rss201rev2Feed(title=self.title % 'rss',
221 219 link=url('public_journal_rss', qualified=True),
222 220 description=_('Public journal'),
223 221 language=self.language,
224 222 ttl=self.ttl)
225 223
226 224 for entry in journal[:self.feed_nr]:
227 225 #tmpl = h.action_parser(entry)[0]
228 226 action, action_extra = h.action_parser(entry, feed=True)
229 227 title = "%s - %s %s" % (entry.user.short_contact, action,
230 228 entry.repository.repo_name)
231 229 desc = action_extra()
232 230 feed.add_item(title=title,
233 231 pubdate=entry.action_date,
234 232 link=url('', qualified=True),
235 233 author_email=entry.user.email,
236 234 author_name=entry.user.full_contact,
237 235 description=desc)
238 236
239 237 response.content_type = feed.mime_type
240 238 return feed.writeString('utf-8')
@@ -1,150 +1,148 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.login
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Login controller for rhodeocode
7 7
8 8 :created_on: Apr 22, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 import formencode
30 28
31 29 from formencode import htmlfill
32 30
33 31 from pylons.i18n.translation import _
34 32 from pylons.controllers.util import abort, redirect
35 33 from pylons import request, response, session, tmpl_context as c, url
36 34
37 35 import rhodecode.lib.helpers as h
38 36 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
39 37 from rhodecode.lib.base import BaseController, render
40 38 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
41 39 from rhodecode.model.user import UserModel
42 40
43 41
44 42 log = logging.getLogger(__name__)
45 43
46 44 class LoginController(BaseController):
47 45
48 46 def __before__(self):
49 47 super(LoginController, self).__before__()
50 48
51 49 def index(self):
52 50 #redirect if already logged in
53 51 c.came_from = request.GET.get('came_from', None)
54 52
55 53 if self.rhodecode_user.is_authenticated \
56 54 and self.rhodecode_user.username != 'default':
57 55
58 56 return redirect(url('home'))
59 57
60 58 if request.POST:
61 59 #import Login Form validator class
62 60 login_form = LoginForm()
63 61 try:
64 62 c.form_result = login_form.to_python(dict(request.POST))
65 63 #form checks for username/password, now we're authenticated
66 64 username = c.form_result['username']
67 65 user = UserModel().get_by_username(username,
68 66 case_insensitive=True)
69 67 auth_user = AuthUser(user.user_id)
70 68 auth_user.set_authenticated()
71 69 session['rhodecode_user'] = auth_user
72 70 session.save()
73 71
74 72 log.info('user %s is now authenticated and stored in session',
75 73 username)
76 74 user.update_lastlogin()
77 75
78 76 if c.came_from:
79 77 return redirect(c.came_from)
80 78 else:
81 79 return redirect(url('home'))
82 80
83 81 except formencode.Invalid, errors:
84 82 return htmlfill.render(
85 83 render('/login.html'),
86 84 defaults=errors.value,
87 85 errors=errors.error_dict or {},
88 86 prefix_error=False,
89 87 encoding="UTF-8")
90 88
91 89 return render('/login.html')
92 90
93 91 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
94 92 'hg.register.manual_activate')
95 93 def register(self):
96 94 user_model = UserModel()
97 95 c.auto_active = False
98 96 for perm in user_model.get_by_username('default', cache=False).user_perms:
99 97 if perm.permission.permission_name == 'hg.register.auto_activate':
100 98 c.auto_active = True
101 99 break
102 100
103 101 if request.POST:
104 102
105 103 register_form = RegisterForm()()
106 104 try:
107 105 form_result = register_form.to_python(dict(request.POST))
108 106 form_result['active'] = c.auto_active
109 107 user_model.create_registration(form_result)
110 108 h.flash(_('You have successfully registered into rhodecode'),
111 109 category='success')
112 110 return redirect(url('login_home'))
113 111
114 112 except formencode.Invalid, errors:
115 113 return htmlfill.render(
116 114 render('/register.html'),
117 115 defaults=errors.value,
118 116 errors=errors.error_dict or {},
119 117 prefix_error=False,
120 118 encoding="UTF-8")
121 119
122 120 return render('/register.html')
123 121
124 122 def password_reset(self):
125 123 user_model = UserModel()
126 124 if request.POST:
127 125
128 126 password_reset_form = PasswordResetForm()()
129 127 try:
130 128 form_result = password_reset_form.to_python(dict(request.POST))
131 129 user_model.reset_password(form_result)
132 130 h.flash(_('Your new password was sent'),
133 131 category='success')
134 132 return redirect(url('login_home'))
135 133
136 134 except formencode.Invalid, errors:
137 135 return htmlfill.render(
138 136 render('/password_reset.html'),
139 137 defaults=errors.value,
140 138 errors=errors.error_dict or {},
141 139 prefix_error=False,
142 140 encoding="UTF-8")
143 141
144 142 return render('/password_reset.html')
145 143
146 144 def logout(self):
147 145 del session['rhodecode_user']
148 146 session.save()
149 147 log.info('Logging out and setting user as Empty')
150 148 redirect(url('home'))
@@ -1,119 +1,117 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.search
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Search controller for rhodecode
7 7
8 8 :created_on: Aug 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25 import logging
28 26 import traceback
29 27
30 28 from pylons.i18n.translation import _
31 29 from pylons import request, config, session, tmpl_context as c
32 30
33 31 from rhodecode.lib.auth import LoginRequired
34 32 from rhodecode.lib.base import BaseController, render
35 33 from rhodecode.lib.indexers import SCHEMA, IDX_NAME, ResultWrapper
36 34
37 35 from webhelpers.paginate import Page
38 36 from webhelpers.util import update_params
39 37
40 38 from whoosh.index import open_dir, EmptyIndexError
41 39 from whoosh.qparser import QueryParser, QueryParserError
42 40 from whoosh.query import Phrase
43 41
44 42 log = logging.getLogger(__name__)
45 43
46 44 class SearchController(BaseController):
47 45
48 46 @LoginRequired()
49 47 def __before__(self):
50 48 super(SearchController, self).__before__()
51 49
52 50 def index(self, search_repo=None):
53 51 c.repo_name = search_repo
54 52 c.formated_results = []
55 53 c.runtime = ''
56 54 c.cur_query = request.GET.get('q', None)
57 55 c.cur_type = request.GET.get('type', 'source')
58 56 c.cur_search = search_type = {'content':'content',
59 57 'commit':'content',
60 58 'path':'path',
61 59 'repository':'repository'}\
62 60 .get(c.cur_type, 'content')
63 61
64 62
65 63 if c.cur_query:
66 64 cur_query = c.cur_query.lower()
67 65
68 66 if c.cur_query:
69 67 p = int(request.params.get('page', 1))
70 68 highlight_items = set()
71 69 try:
72 70 idx = open_dir(config['app_conf']['index_dir']
73 71 , indexname=IDX_NAME)
74 72 searcher = idx.searcher()
75 73
76 74 qp = QueryParser(search_type, schema=SCHEMA)
77 75 if c.repo_name:
78 76 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
79 77 try:
80 78 query = qp.parse(unicode(cur_query))
81 79
82 80 if isinstance(query, Phrase):
83 81 highlight_items.update(query.words)
84 82 else:
85 83 for i in query.all_terms():
86 84 if i[0] == 'content':
87 85 highlight_items.add(i[1])
88 86
89 87 matcher = query.matcher(searcher)
90 88
91 89 log.debug(query)
92 90 log.debug(highlight_items)
93 91 results = searcher.search(query)
94 92 res_ln = len(results)
95 93 c.runtime = '%s results (%.3f seconds)' \
96 94 % (res_ln, results.runtime)
97 95
98 96 def url_generator(**kw):
99 97 return update_params("?q=%s&type=%s" \
100 98 % (c.cur_query, c.cur_search), **kw)
101 99
102 100 c.formated_results = Page(
103 101 ResultWrapper(search_type, searcher, matcher,
104 102 highlight_items),
105 103 page=p, item_count=res_ln,
106 104 items_per_page=10, url=url_generator)
107 105
108 106
109 107 except QueryParserError:
110 108 c.runtime = _('Invalid search query. Try quoting it.')
111 109 searcher.close()
112 110 except (EmptyIndexError, IOError):
113 111 log.error(traceback.format_exc())
114 112 log.error('Empty Index data')
115 113 c.runtime = _('There is no index to search in. '
116 114 'Please run whoosh indexer')
117 115
118 116 # Return a rendered template
119 117 return render('/search/search.html')
@@ -1,209 +1,207 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Settings controller for rhodecode
7 7
8 8 :created_on: Jun 30, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 import traceback
30 28 import formencode
31 29
32 30 from formencode import htmlfill
33 31
34 32 from pylons import tmpl_context as c, request, url
35 33 from pylons.controllers.util import redirect
36 34 from pylons.i18n.translation import _
37 35
38 36 import rhodecode.lib.helpers as h
39 37
40 38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
41 39 HasRepoPermissionAnyDecorator, NotAnonymous
42 40 from rhodecode.lib.base import BaseRepoController, render
43 41 from rhodecode.lib.utils import invalidate_cache, action_logger
44 42
45 43 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
46 44 from rhodecode.model.repo import RepoModel
47 45 from rhodecode.model.db import User
48 46
49 47 log = logging.getLogger(__name__)
50 48
51 49 class SettingsController(BaseRepoController):
52 50
53 51 @LoginRequired()
54 52 def __before__(self):
55 53 super(SettingsController, self).__before__()
56 54
57 55 @HasRepoPermissionAllDecorator('repository.admin')
58 56 def index(self, repo_name):
59 57 repo_model = RepoModel()
60 58 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
61 59 if not repo:
62 60 h.flash(_('%s repository is not mapped to db perhaps'
63 61 ' it was created or renamed from the file system'
64 62 ' please run the application again'
65 63 ' in order to rescan repositories') % repo_name,
66 64 category='error')
67 65
68 66 return redirect(url('home'))
69 67
70 68 c.users_array = repo_model.get_users_js()
71 69 c.users_groups_array = repo_model.get_users_groups_js()
72 70
73 71 defaults = c.repo_info.get_dict()
74 72
75 73 #fill owner
76 74 if c.repo_info.user:
77 75 defaults.update({'user':c.repo_info.user.username})
78 76 else:
79 77 replacement_user = self.sa.query(User)\
80 78 .filter(User.admin == True).first().username
81 79 defaults.update({'user':replacement_user})
82 80
83 81 #fill repository users
84 82 for p in c.repo_info.repo_to_perm:
85 83 defaults.update({'u_perm_%s' % p.user.username:
86 84 p.permission.permission_name})
87 85
88 86 #fill repository groups
89 87 for p in c.repo_info.users_group_to_perm:
90 88 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
91 89 p.permission.permission_name})
92 90
93 91 return htmlfill.render(
94 92 render('settings/repo_settings.html'),
95 93 defaults=defaults,
96 94 encoding="UTF-8",
97 95 force_defaults=False
98 96 )
99 97
100 98 @HasRepoPermissionAllDecorator('repository.admin')
101 99 def update(self, repo_name):
102 100 repo_model = RepoModel()
103 101 changed_name = repo_name
104 102 _form = RepoSettingsForm(edit=True, old_data={'repo_name':repo_name})()
105 103 try:
106 104 form_result = _form.to_python(dict(request.POST))
107 105 repo_model.update(repo_name, form_result)
108 106 invalidate_cache('get_repo_cached_%s' % repo_name)
109 107 h.flash(_('Repository %s updated successfully' % repo_name),
110 108 category='success')
111 109 changed_name = form_result['repo_name']
112 110 action_logger(self.rhodecode_user, 'user_updated_repo',
113 111 changed_name, '', self.sa)
114 112 except formencode.Invalid, errors:
115 113 c.repo_info = repo_model.get_by_repo_name(repo_name)
116 114 c.users_array = repo_model.get_users_js()
117 115 errors.value.update({'user':c.repo_info.user.username})
118 116 return htmlfill.render(
119 117 render('settings/repo_settings.html'),
120 118 defaults=errors.value,
121 119 errors=errors.error_dict or {},
122 120 prefix_error=False,
123 121 encoding="UTF-8")
124 122 except Exception:
125 123 log.error(traceback.format_exc())
126 124 h.flash(_('error occurred during update of repository %s') \
127 125 % repo_name, category='error')
128 126
129 127 return redirect(url('repo_settings_home', repo_name=changed_name))
130 128
131 129
132 130 @HasRepoPermissionAllDecorator('repository.admin')
133 131 def delete(self, repo_name):
134 132 """DELETE /repos/repo_name: Delete an existing item"""
135 133 # Forms posted to this method should contain a hidden field:
136 134 # <input type="hidden" name="_method" value="DELETE" />
137 135 # Or using helpers:
138 136 # h.form(url('repo_settings_delete', repo_name=ID),
139 137 # method='delete')
140 138 # url('repo_settings_delete', repo_name=ID)
141 139
142 140 repo_model = RepoModel()
143 141 repo = repo_model.get_by_repo_name(repo_name)
144 142 if not repo:
145 143 h.flash(_('%s repository is not mapped to db perhaps'
146 144 ' it was moved or renamed from the filesystem'
147 145 ' please run the application again'
148 146 ' in order to rescan repositories') % repo_name,
149 147 category='error')
150 148
151 149 return redirect(url('home'))
152 150 try:
153 151 action_logger(self.rhodecode_user, 'user_deleted_repo',
154 152 repo_name, '', self.sa)
155 153 repo_model.delete(repo)
156 154 invalidate_cache('get_repo_cached_%s' % repo_name)
157 155 h.flash(_('deleted repository %s') % repo_name, category='success')
158 156 except Exception:
159 157 h.flash(_('An error occurred during deletion of %s') % repo_name,
160 158 category='error')
161 159
162 160 return redirect(url('home'))
163 161
164 162 @NotAnonymous()
165 163 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
166 164 'repository.admin')
167 165 def fork(self, repo_name):
168 166 repo_model = RepoModel()
169 167 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
170 168 if not repo:
171 169 h.flash(_('%s repository is not mapped to db perhaps'
172 170 ' it was created or renamed from the file system'
173 171 ' please run the application again'
174 172 ' in order to rescan repositories') % repo_name,
175 173 category='error')
176 174
177 175 return redirect(url('home'))
178 176
179 177 return render('settings/repo_fork.html')
180 178
181 179 @NotAnonymous()
182 180 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
183 181 'repository.admin')
184 182 def fork_create(self, repo_name):
185 183 repo_model = RepoModel()
186 184 c.repo_info = repo_model.get_by_repo_name(repo_name)
187 185 _form = RepoForkForm(old_data={'repo_type':c.repo_info.repo_type})()
188 186 form_result = {}
189 187 try:
190 188 form_result = _form.to_python(dict(request.POST))
191 189 form_result.update({'repo_name':repo_name})
192 190 repo_model.create_fork(form_result, self.rhodecode_user)
193 191 h.flash(_('forked %s repository as %s') \
194 192 % (repo_name, form_result['fork_name']),
195 193 category='success')
196 194 action_logger(self.rhodecode_user,
197 195 'user_forked_repo:%s' % form_result['fork_name'],
198 196 repo_name, '', self.sa)
199 197 except formencode.Invalid, errors:
200 198 c.new_repo = errors.value['fork_name']
201 199 r = render('settings/repo_fork.html')
202 200
203 201 return htmlfill.render(
204 202 r,
205 203 defaults=errors.value,
206 204 errors=errors.error_dict or {},
207 205 prefix_error=False,
208 206 encoding="UTF-8")
209 207 return redirect(url('home'))
@@ -1,56 +1,54 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.shortlog
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Shortlog controller for rhodecode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27
30 28 from pylons import tmpl_context as c, request
31 29
32 30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 31 from rhodecode.lib.base import BaseRepoController, render
34 32 from rhodecode.lib.helpers import RepoPage
35 33
36 34 log = logging.getLogger(__name__)
37 35
38 36
39 37
40 38
41 39 class ShortlogController(BaseRepoController):
42 40
43 41 @LoginRequired()
44 42 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
45 43 'repository.admin')
46 44 def __before__(self):
47 45 super(ShortlogController, self).__before__()
48 46
49 47 def index(self):
50 48 p = int(request.params.get('page', 1))
51 49 c.repo_changesets = RepoPage(c.rhodecode_repo, page=p, items_per_page=20)
52 50 c.shortlog_data = render('shortlog/shortlog_data.html')
53 51 if request.params.get('partial'):
54 52 return c.shortlog_data
55 53 r = render('shortlog/shortlog.html')
56 54 return r
@@ -1,180 +1,178 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.summary
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Summary controller for Rhodecode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import calendar
29 27 import logging
30 28 from time import mktime
31 29 from datetime import datetime, timedelta, date
32 30
33 31 from vcs.exceptions import ChangesetError
34 32
35 33 from pylons import tmpl_context as c, request, url
36 34 from pylons.i18n.translation import _
37 35
38 36 from rhodecode.model.db import Statistics, Repository
39 37 from rhodecode.model.repo import RepoModel
40 38
41 39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 40 from rhodecode.lib.base import BaseRepoController, render
43 41 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
44 42
45 43 from rhodecode.lib.celerylib import run_task
46 44 from rhodecode.lib.celerylib.tasks import get_commits_stats
47 45 from rhodecode.lib.helpers import RepoPage
48 46
49 47 try:
50 48 import json
51 49 except ImportError:
52 50 #python 2.5 compatibility
53 51 import simplejson as json
54 52 log = logging.getLogger(__name__)
55 53
56 54 class SummaryController(BaseRepoController):
57 55
58 56 @LoginRequired()
59 57 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
60 58 'repository.admin')
61 59 def __before__(self):
62 60 super(SummaryController, self).__before__()
63 61
64 62 def index(self, repo_name):
65 63
66 64 e = request.environ
67 65 c.dbrepo = dbrepo = Repository.by_repo_name(repo_name)
68 66
69 67 c.following = self.scm_model.is_following_repo(repo_name,
70 68 self.rhodecode_user.user_id)
71 69 def url_generator(**kw):
72 70 return url('shortlog_home', repo_name=repo_name, **kw)
73 71
74 72 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1, items_per_page=10,
75 73 url=url_generator)
76 74
77 75
78 76
79 77 if self.rhodecode_user.username == 'default':
80 78 #for default(anonymous) user we don't need to pass credentials
81 79 username = ''
82 80 password = ''
83 81 else:
84 82 username = str(self.rhodecode_user.username)
85 83 password = '@'
86 84
87 85 uri = u'%(protocol)s://%(user)s%(password)s%(host)s%(prefix)s/%(repo_name)s' % {
88 86 'protocol': e.get('wsgi.url_scheme'),
89 87 'user':username,
90 88 'password':password,
91 89 'host':e.get('HTTP_HOST'),
92 90 'prefix':e.get('SCRIPT_NAME'),
93 91 'repo_name':repo_name, }
94 92 c.clone_repo_url = uri
95 93 c.repo_tags = OrderedDict()
96 94 for name, hash in c.rhodecode_repo.tags.items()[:10]:
97 95 try:
98 96 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
99 97 except ChangesetError:
100 98 c.repo_tags[name] = EmptyChangeset(hash)
101 99
102 100 c.repo_branches = OrderedDict()
103 101 for name, hash in c.rhodecode_repo.branches.items()[:10]:
104 102 try:
105 103 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
106 104 except ChangesetError:
107 105 c.repo_branches[name] = EmptyChangeset(hash)
108 106
109 107 td = date.today() + timedelta(days=1)
110 108 td_1m = td - timedelta(days=calendar.mdays[td.month])
111 109 td_1y = td - timedelta(days=365)
112 110
113 111 ts_min_m = mktime(td_1m.timetuple())
114 112 ts_min_y = mktime(td_1y.timetuple())
115 113 ts_max_y = mktime(td.timetuple())
116 114
117 115 if dbrepo.enable_statistics:
118 116 c.no_data_msg = _('No data loaded yet')
119 117 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
120 118 else:
121 119 c.no_data_msg = _('Statistics are disabled for this repository')
122 120 c.ts_min = ts_min_m
123 121 c.ts_max = ts_max_y
124 122
125 123 stats = self.sa.query(Statistics)\
126 124 .filter(Statistics.repository == dbrepo)\
127 125 .scalar()
128 126
129 127 c.stats_percentage = 0
130 128
131 129 if stats and stats.languages:
132 130 c.no_data = False is dbrepo.enable_statistics
133 131 lang_stats = json.loads(stats.languages)
134 132 c.commit_data = stats.commit_activity
135 133 c.overview_data = stats.commit_activity_combined
136 134 c.trending_languages = json.dumps(OrderedDict(
137 135 sorted(lang_stats.items(), reverse=True,
138 136 key=lambda k: k[1])[:10]
139 137 )
140 138 )
141 139 last_rev = stats.stat_on_revision
142 140 c.repo_last_rev = c.rhodecode_repo.count() - 1 \
143 141 if c.rhodecode_repo.revisions else 0
144 142 if last_rev == 0 or c.repo_last_rev == 0:
145 143 pass
146 144 else:
147 145 c.stats_percentage = '%.2f' % ((float((last_rev)) /
148 146 c.repo_last_rev) * 100)
149 147 else:
150 148 c.commit_data = json.dumps({})
151 149 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
152 150 c.trending_languages = json.dumps({})
153 151 c.no_data = True
154 152
155 153 c.enable_downloads = dbrepo.enable_downloads
156 154 if c.enable_downloads:
157 155 c.download_options = self._get_download_links(c.rhodecode_repo)
158 156
159 157 return render('summary/summary.html')
160 158
161 159
162 160
163 161 def _get_download_links(self, repo):
164 162
165 163 download_l = []
166 164
167 165 branches_group = ([], _("Branches"))
168 166 tags_group = ([], _("Tags"))
169 167
170 168 for name, chs in c.rhodecode_repo.branches.items():
171 169 #chs = chs.split(':')[-1]
172 170 branches_group[0].append((chs, name),)
173 171 download_l.append(branches_group)
174 172
175 173 for name, chs in c.rhodecode_repo.tags.items():
176 174 #chs = chs.split(':')[-1]
177 175 tags_group[0].append((chs, name),)
178 176 download_l.append(tags_group)
179 177
180 178 return download_l
@@ -1,54 +1,52 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.tags
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Tags controller for rhodecode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25 import logging
28 26
29 27 from pylons import tmpl_context as c
30 28
31 29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 30 from rhodecode.lib.base import BaseRepoController, render
33 31 from rhodecode.lib.utils import OrderedDict
34 32
35 33 log = logging.getLogger(__name__)
36 34
37 35 class TagsController(BaseRepoController):
38 36
39 37 @LoginRequired()
40 38 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 39 'repository.admin')
42 40 def __before__(self):
43 41 super(TagsController, self).__before__()
44 42
45 43 def index(self):
46 44 c.repo_tags = OrderedDict()
47 45
48 46 tags = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
49 47 name, hash_ in c.rhodecode_repo.tags.items()]
50 48 ordered_tags = sorted(tags, key=lambda x:x[1].date, reverse=True)
51 49 for name, cs_tag in ordered_tags:
52 50 c.repo_tags[name] = cs_tag
53 51
54 52 return render('tags/tags.html')
@@ -1,70 +1,68 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Some simple helper functions
7 7
8 8 :created_on: Jan 5, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 def str2bool(s):
29 27 if s is None:
30 28 return False
31 29 if s in (True, False):
32 30 return s
33 31 s = str(s).strip().lower()
34 32 return s in ('t', 'true', 'y', 'yes', 'on', '1')
35 33
36 34 def generate_api_key(username, salt=None):
37 35 """
38 36 Generates uniq API key for given username
39 37
40 38 :param username: username as string
41 39 :param salt: salt to hash generate KEY
42 40 """
43 41 from tempfile import _RandomNameSequence
44 42 import hashlib
45 43
46 44 if salt is None:
47 45 salt = _RandomNameSequence().next()
48 46
49 47 return hashlib.sha1(username + salt).hexdigest()
50 48
51 49 def safe_unicode(_str):
52 50 """
53 51 safe unicode function. In case of UnicodeDecode error we try to return
54 52 unicode with errors replace, if this fails we return unicode with
55 53 string_escape decoding
56 54 """
57 55
58 56 if isinstance(_str, unicode):
59 57 return _str
60 58
61 59 try:
62 60 u_str = unicode(_str)
63 61 except UnicodeDecodeError:
64 62 try:
65 63 u_str = _str.decode('utf-8', 'replace')
66 64 except UnicodeDecodeError:
67 65 #incase we have a decode error just represent as byte string
68 66 u_str = unicode(_str.encode('string_escape'))
69 67
70 68 return u_str
@@ -1,588 +1,586 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.auth
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 authentication and permission libraries
7 7
8 8 :created_on: Apr 4, 2010
9 9 :copyright: (c) 2010 by marcink.
10 10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 11 """
12 # This program is free software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; version 2
15 # of the License or (at your opinion) any later version of the license.
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
16 16 #
17 17 # This program is distributed in the hope that it will be useful,
18 18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 20 # GNU General Public License for more details.
21 21 #
22 22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # MA 02110-1301, USA.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 24
27 25 import random
28 26 import logging
29 27 import traceback
30 28 import hashlib
31 29
32 30 from tempfile import _RandomNameSequence
33 31 from decorator import decorator
34 32
35 33 from pylons import config, session, url, request
36 34 from pylons.controllers.util import abort, redirect
37 35 from pylons.i18n.translation import _
38 36
39 37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
40 38
41 39 if __platform__ in PLATFORM_WIN:
42 40 from hashlib import sha256
43 41 if __platform__ in PLATFORM_OTHERS:
44 42 import bcrypt
45 43
46 44 from rhodecode.lib import str2bool
47 45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 46 from rhodecode.lib.utils import get_repo_slug
49 47 from rhodecode.lib.auth_ldap import AuthLdap
50 48
51 49 from rhodecode.model import meta
52 50 from rhodecode.model.user import UserModel
53 51 from rhodecode.model.db import Permission
54 52
55 53
56 54 log = logging.getLogger(__name__)
57 55
58 56 class PasswordGenerator(object):
59 57 """This is a simple class for generating password from
60 58 different sets of characters
61 59 usage:
62 60 passwd_gen = PasswordGenerator()
63 61 #print 8-letter password containing only big and small letters of alphabet
64 62 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
65 63 """
66 64 ALPHABETS_NUM = r'''1234567890'''#[0]
67 65 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
68 66 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
69 67 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
70 68 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
71 69 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
72 70 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 71 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
74 72 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
75 73
76 74 def __init__(self, passwd=''):
77 75 self.passwd = passwd
78 76
79 77 def gen_password(self, len, type):
80 78 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
81 79 return self.passwd
82 80
83 81 class RhodeCodeCrypto(object):
84 82
85 83 @classmethod
86 84 def hash_string(cls, str_):
87 85 """
88 86 Cryptographic function used for password hashing based on pybcrypt
89 87 or pycrypto in windows
90 88
91 89 :param password: password to hash
92 90 """
93 91 if __platform__ in PLATFORM_WIN:
94 92 return sha256(str_).hexdigest()
95 93 elif __platform__ in PLATFORM_OTHERS:
96 94 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
97 95 else:
98 96 raise Exception('Unknown or unsupported platform %s' % __platform__)
99 97
100 98 @classmethod
101 99 def hash_check(cls, password, hashed):
102 100 """
103 101 Checks matching password with it's hashed value, runs different
104 102 implementation based on platform it runs on
105 103
106 104 :param password: password
107 105 :param hashed: password in hashed form
108 106 """
109 107
110 108 if __platform__ in PLATFORM_WIN:
111 109 return sha256(password).hexdigest() == hashed
112 110 elif __platform__ in PLATFORM_OTHERS:
113 111 return bcrypt.hashpw(password, hashed) == hashed
114 112 else:
115 113 raise Exception('Unknown or unsupported platform %s' % __platform__)
116 114
117 115
118 116 def get_crypt_password(password):
119 117 return RhodeCodeCrypto.hash_string(password)
120 118
121 119 def check_password(password, hashed):
122 120 return RhodeCodeCrypto.hash_check(password, hashed)
123 121
124 122 def generate_api_key(username, salt=None):
125 123 if salt is None:
126 124 salt = _RandomNameSequence().next()
127 125
128 126 return hashlib.sha1(username + salt).hexdigest()
129 127
130 128 def authfunc(environ, username, password):
131 129 """Dummy authentication function used in Mercurial/Git/ and access control,
132 130
133 131 :param environ: needed only for using in Basic auth
134 132 """
135 133 return authenticate(username, password)
136 134
137 135
138 136 def authenticate(username, password):
139 137 """Authentication function used for access control,
140 138 firstly checks for db authentication then if ldap is enabled for ldap
141 139 authentication, also creates ldap user if not in database
142 140
143 141 :param username: username
144 142 :param password: password
145 143 """
146 144 user_model = UserModel()
147 145 user = user_model.get_by_username(username, cache=False)
148 146
149 147 log.debug('Authenticating user using RhodeCode account')
150 148 if user is not None and not user.ldap_dn:
151 149 if user.active:
152 150
153 151 if user.username == 'default' and user.active:
154 152 log.info('user %s authenticated correctly as anonymous user',
155 153 username)
156 154 return True
157 155
158 156 elif user.username == username and check_password(password, user.password):
159 157 log.info('user %s authenticated correctly', username)
160 158 return True
161 159 else:
162 160 log.warning('user %s is disabled', username)
163 161
164 162 else:
165 163 log.debug('Regular authentication failed')
166 164 user_obj = user_model.get_by_username(username, cache=False,
167 165 case_insensitive=True)
168 166
169 167 if user_obj is not None and not user_obj.ldap_dn:
170 168 log.debug('this user already exists as non ldap')
171 169 return False
172 170
173 171 from rhodecode.model.settings import SettingsModel
174 172 ldap_settings = SettingsModel().get_ldap_settings()
175 173
176 174 #======================================================================
177 175 # FALLBACK TO LDAP AUTH IF ENABLE
178 176 #======================================================================
179 177 if str2bool(ldap_settings.get('ldap_active')):
180 178 log.debug("Authenticating user using ldap")
181 179 kwargs = {
182 180 'server':ldap_settings.get('ldap_host', ''),
183 181 'base_dn':ldap_settings.get('ldap_base_dn', ''),
184 182 'port':ldap_settings.get('ldap_port'),
185 183 'bind_dn':ldap_settings.get('ldap_dn_user'),
186 184 'bind_pass':ldap_settings.get('ldap_dn_pass'),
187 185 'use_ldaps':str2bool(ldap_settings.get('ldap_ldaps')),
188 186 'tls_reqcert':ldap_settings.get('ldap_tls_reqcert'),
189 187 'ldap_filter':ldap_settings.get('ldap_filter'),
190 188 'search_scope':ldap_settings.get('ldap_search_scope'),
191 189 'attr_login':ldap_settings.get('ldap_attr_login'),
192 190 'ldap_version':3,
193 191 }
194 192 log.debug('Checking for ldap authentication')
195 193 try:
196 194 aldap = AuthLdap(**kwargs)
197 195 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
198 196 log.debug('Got ldap DN response %s', user_dn)
199 197
200 198 user_attrs = {
201 199 'name' : ldap_attrs[ldap_settings.get('ldap_attr_firstname')][0],
202 200 'lastname' : ldap_attrs[ldap_settings.get('ldap_attr_lastname')][0],
203 201 'email' : ldap_attrs[ldap_settings.get('ldap_attr_email')][0],
204 202 }
205 203
206 204 if user_model.create_ldap(username, password, user_dn, user_attrs):
207 205 log.info('created new ldap user %s', username)
208 206
209 207 return True
210 208 except (LdapUsernameError, LdapPasswordError,):
211 209 pass
212 210 except (Exception,):
213 211 log.error(traceback.format_exc())
214 212 pass
215 213 return False
216 214
217 215 class AuthUser(object):
218 216 """
219 217 A simple object that handles all attributes of user in RhodeCode
220 218
221 219 It does lookup based on API key,given user, or user present in session
222 220 Then it fills all required information for such user. It also checks if
223 221 anonymous access is enabled and if so, it returns default user as logged
224 222 in
225 223 """
226 224
227 225 def __init__(self, user_id=None, api_key=None):
228 226
229 227 self.user_id = user_id
230 228 self.api_key = None
231 229
232 230 self.username = 'None'
233 231 self.name = ''
234 232 self.lastname = ''
235 233 self.email = ''
236 234 self.is_authenticated = False
237 235 self.admin = False
238 236 self.permissions = {}
239 237 self._api_key = api_key
240 238 self.propagate_data()
241 239
242 240
243 241 def propagate_data(self):
244 242 user_model = UserModel()
245 243 self.anonymous_user = user_model.get_by_username('default', cache=True)
246 244 if self._api_key and self._api_key != self.anonymous_user.api_key:
247 245 #try go get user by api key
248 246 log.debug('Auth User lookup by API KEY %s', self._api_key)
249 247 user_model.fill_data(self, api_key=self._api_key)
250 248 else:
251 249 log.debug('Auth User lookup by USER ID %s', self.user_id)
252 250 if self.user_id is not None and self.user_id != self.anonymous_user.user_id:
253 251 user_model.fill_data(self, user_id=self.user_id)
254 252 else:
255 253 if self.anonymous_user.active is True:
256 254 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
257 255 #then we set this user is logged in
258 256 self.is_authenticated = True
259 257 else:
260 258 self.is_authenticated = False
261 259
262 260 log.debug('Auth User is now %s', self)
263 261 user_model.fill_perms(self)
264 262
265 263 @property
266 264 def is_admin(self):
267 265 return self.admin
268 266
269 267 def __repr__(self):
270 268 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
271 269 self.is_authenticated)
272 270
273 271 def set_authenticated(self, authenticated=True):
274 272
275 273 if self.user_id != self.anonymous_user.user_id:
276 274 self.is_authenticated = authenticated
277 275
278 276
279 277 def set_available_permissions(config):
280 278 """This function will propagate pylons globals with all available defined
281 279 permission given in db. We don't want to check each time from db for new
282 280 permissions since adding a new permission also requires application restart
283 281 ie. to decorate new views with the newly created permission
284 282
285 283 :param config: current pylons config instance
286 284
287 285 """
288 286 log.info('getting information about all available permissions')
289 287 try:
290 288 sa = meta.Session()
291 289 all_perms = sa.query(Permission).all()
292 290 except:
293 291 pass
294 292 finally:
295 293 meta.Session.remove()
296 294
297 295 config['available_permissions'] = [x.permission_name for x in all_perms]
298 296
299 297 #===============================================================================
300 298 # CHECK DECORATORS
301 299 #===============================================================================
302 300 class LoginRequired(object):
303 301 """
304 302 Must be logged in to execute this function else
305 303 redirect to login page
306 304
307 305 :param api_access: if enabled this checks only for valid auth token
308 306 and grants access based on valid token
309 307 """
310 308
311 309 def __init__(self, api_access=False):
312 310 self.api_access = api_access
313 311
314 312 def __call__(self, func):
315 313 return decorator(self.__wrapper, func)
316 314
317 315 def __wrapper(self, func, *fargs, **fkwargs):
318 316 cls = fargs[0]
319 317 user = cls.rhodecode_user
320 318
321 319 api_access_ok = False
322 320 if self.api_access:
323 321 log.debug('Checking API KEY access for %s', cls)
324 322 if user.api_key == request.GET.get('api_key'):
325 323 api_access_ok = True
326 324 else:
327 325 log.debug("API KEY token not valid")
328 326
329 327 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
330 328 if user.is_authenticated or api_access_ok:
331 329 log.debug('user %s is authenticated', user.username)
332 330 return func(*fargs, **fkwargs)
333 331 else:
334 332 log.warn('user %s NOT authenticated', user.username)
335 333
336 334 p = ''
337 335 if request.environ.get('SCRIPT_NAME') != '/':
338 336 p += request.environ.get('SCRIPT_NAME')
339 337
340 338 p += request.environ.get('PATH_INFO')
341 339 if request.environ.get('QUERY_STRING'):
342 340 p += '?' + request.environ.get('QUERY_STRING')
343 341
344 342 log.debug('redirecting to login page with %s', p)
345 343 return redirect(url('login_home', came_from=p))
346 344
347 345 class NotAnonymous(object):
348 346 """Must be logged in to execute this function else
349 347 redirect to login page"""
350 348
351 349 def __call__(self, func):
352 350 return decorator(self.__wrapper, func)
353 351
354 352 def __wrapper(self, func, *fargs, **fkwargs):
355 353 cls = fargs[0]
356 354 self.user = cls.rhodecode_user
357 355
358 356 log.debug('Checking if user is not anonymous @%s', cls)
359 357
360 358 anonymous = self.user.username == 'default'
361 359
362 360 if anonymous:
363 361 p = ''
364 362 if request.environ.get('SCRIPT_NAME') != '/':
365 363 p += request.environ.get('SCRIPT_NAME')
366 364
367 365 p += request.environ.get('PATH_INFO')
368 366 if request.environ.get('QUERY_STRING'):
369 367 p += '?' + request.environ.get('QUERY_STRING')
370 368
371 369 import rhodecode.lib.helpers as h
372 370 h.flash(_('You need to be a registered user to perform this action'),
373 371 category='warning')
374 372 return redirect(url('login_home', came_from=p))
375 373 else:
376 374 return func(*fargs, **fkwargs)
377 375
378 376 class PermsDecorator(object):
379 377 """Base class for controller decorators"""
380 378
381 379 def __init__(self, *required_perms):
382 380 available_perms = config['available_permissions']
383 381 for perm in required_perms:
384 382 if perm not in available_perms:
385 383 raise Exception("'%s' permission is not defined" % perm)
386 384 self.required_perms = set(required_perms)
387 385 self.user_perms = None
388 386
389 387 def __call__(self, func):
390 388 return decorator(self.__wrapper, func)
391 389
392 390
393 391 def __wrapper(self, func, *fargs, **fkwargs):
394 392 cls = fargs[0]
395 393 self.user = cls.rhodecode_user
396 394 self.user_perms = self.user.permissions
397 395 log.debug('checking %s permissions %s for %s %s',
398 396 self.__class__.__name__, self.required_perms, cls,
399 397 self.user)
400 398
401 399 if self.check_permissions():
402 400 log.debug('Permission granted for %s %s', cls, self.user)
403 401 return func(*fargs, **fkwargs)
404 402
405 403 else:
406 404 log.warning('Permission denied for %s %s', cls, self.user)
407 405 #redirect with forbidden ret code
408 406 return abort(403)
409 407
410 408
411 409
412 410 def check_permissions(self):
413 411 """Dummy function for overriding"""
414 412 raise Exception('You have to write this function in child class')
415 413
416 414 class HasPermissionAllDecorator(PermsDecorator):
417 415 """Checks for access permission for all given predicates. All of them
418 416 have to be meet in order to fulfill the request
419 417 """
420 418
421 419 def check_permissions(self):
422 420 if self.required_perms.issubset(self.user_perms.get('global')):
423 421 return True
424 422 return False
425 423
426 424
427 425 class HasPermissionAnyDecorator(PermsDecorator):
428 426 """Checks for access permission for any of given predicates. In order to
429 427 fulfill the request any of predicates must be meet
430 428 """
431 429
432 430 def check_permissions(self):
433 431 if self.required_perms.intersection(self.user_perms.get('global')):
434 432 return True
435 433 return False
436 434
437 435 class HasRepoPermissionAllDecorator(PermsDecorator):
438 436 """Checks for access permission for all given predicates for specific
439 437 repository. All of them have to be meet in order to fulfill the request
440 438 """
441 439
442 440 def check_permissions(self):
443 441 repo_name = get_repo_slug(request)
444 442 try:
445 443 user_perms = set([self.user_perms['repositories'][repo_name]])
446 444 except KeyError:
447 445 return False
448 446 if self.required_perms.issubset(user_perms):
449 447 return True
450 448 return False
451 449
452 450
453 451 class HasRepoPermissionAnyDecorator(PermsDecorator):
454 452 """Checks for access permission for any of given predicates for specific
455 453 repository. In order to fulfill the request any of predicates must be meet
456 454 """
457 455
458 456 def check_permissions(self):
459 457 repo_name = get_repo_slug(request)
460 458
461 459 try:
462 460 user_perms = set([self.user_perms['repositories'][repo_name]])
463 461 except KeyError:
464 462 return False
465 463 if self.required_perms.intersection(user_perms):
466 464 return True
467 465 return False
468 466 #===============================================================================
469 467 # CHECK FUNCTIONS
470 468 #===============================================================================
471 469
472 470 class PermsFunction(object):
473 471 """Base function for other check functions"""
474 472
475 473 def __init__(self, *perms):
476 474 available_perms = config['available_permissions']
477 475
478 476 for perm in perms:
479 477 if perm not in available_perms:
480 478 raise Exception("'%s' permission in not defined" % perm)
481 479 self.required_perms = set(perms)
482 480 self.user_perms = None
483 481 self.granted_for = ''
484 482 self.repo_name = None
485 483
486 484 def __call__(self, check_Location=''):
487 485 user = session.get('rhodecode_user', False)
488 486 if not user:
489 487 return False
490 488 self.user_perms = user.permissions
491 489 self.granted_for = user
492 490 log.debug('checking %s %s %s', self.__class__.__name__,
493 491 self.required_perms, user)
494 492
495 493 if self.check_permissions():
496 494 log.debug('Permission granted %s @ %s', self.granted_for,
497 495 check_Location or 'unspecified location')
498 496 return True
499 497
500 498 else:
501 499 log.warning('Permission denied for %s @ %s', self.granted_for,
502 500 check_Location or 'unspecified location')
503 501 return False
504 502
505 503 def check_permissions(self):
506 504 """Dummy function for overriding"""
507 505 raise Exception('You have to write this function in child class')
508 506
509 507 class HasPermissionAll(PermsFunction):
510 508 def check_permissions(self):
511 509 if self.required_perms.issubset(self.user_perms.get('global')):
512 510 return True
513 511 return False
514 512
515 513 class HasPermissionAny(PermsFunction):
516 514 def check_permissions(self):
517 515 if self.required_perms.intersection(self.user_perms.get('global')):
518 516 return True
519 517 return False
520 518
521 519 class HasRepoPermissionAll(PermsFunction):
522 520
523 521 def __call__(self, repo_name=None, check_Location=''):
524 522 self.repo_name = repo_name
525 523 return super(HasRepoPermissionAll, self).__call__(check_Location)
526 524
527 525 def check_permissions(self):
528 526 if not self.repo_name:
529 527 self.repo_name = get_repo_slug(request)
530 528
531 529 try:
532 530 self.user_perms = set([self.user_perms['repositories']\
533 531 [self.repo_name]])
534 532 except KeyError:
535 533 return False
536 534 self.granted_for = self.repo_name
537 535 if self.required_perms.issubset(self.user_perms):
538 536 return True
539 537 return False
540 538
541 539 class HasRepoPermissionAny(PermsFunction):
542 540
543 541 def __call__(self, repo_name=None, check_Location=''):
544 542 self.repo_name = repo_name
545 543 return super(HasRepoPermissionAny, self).__call__(check_Location)
546 544
547 545 def check_permissions(self):
548 546 if not self.repo_name:
549 547 self.repo_name = get_repo_slug(request)
550 548
551 549 try:
552 550 self.user_perms = set([self.user_perms['repositories']\
553 551 [self.repo_name]])
554 552 except KeyError:
555 553 return False
556 554 self.granted_for = self.repo_name
557 555 if self.required_perms.intersection(self.user_perms):
558 556 return True
559 557 return False
560 558
561 559 #===============================================================================
562 560 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
563 561 #===============================================================================
564 562
565 563 class HasPermissionAnyMiddleware(object):
566 564 def __init__(self, *perms):
567 565 self.required_perms = set(perms)
568 566
569 567 def __call__(self, user, repo_name):
570 568 usr = AuthUser(user.user_id)
571 569 try:
572 570 self.user_perms = set([usr.permissions['repositories'][repo_name]])
573 571 except:
574 572 self.user_perms = set()
575 573 self.granted_for = ''
576 574 self.username = user.username
577 575 self.repo_name = repo_name
578 576 return self.check_permissions()
579 577
580 578 def check_permissions(self):
581 579 log.debug('checking mercurial protocol '
582 580 'permissions %s for user:%s repository:%s', self.user_perms,
583 581 self.username, self.repo_name)
584 582 if self.required_perms.intersection(self.user_perms):
585 583 log.debug('permission granted')
586 584 return True
587 585 log.debug('permission denied')
588 586 return False
@@ -1,130 +1,128 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # ldap authentication lib
4 4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 18 """
21 19 Created on Nov 17, 2010
22 20
23 21 @author: marcink
24 22 """
25 23
26 24 from rhodecode.lib.exceptions import *
27 25 import logging
28 26
29 27 log = logging.getLogger(__name__)
30 28
31 29 try:
32 30 import ldap
33 31 except ImportError:
34 32 pass
35 33
36 34 class AuthLdap(object):
37 35
38 36 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
39 37 use_ldaps=False, tls_reqcert='DEMAND', ldap_version=3,
40 38 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
41 39 search_scope='SUBTREE',
42 40 attr_login='uid'):
43 41 self.ldap_version = ldap_version
44 42 if use_ldaps:
45 43 port = port or 689
46 44 self.LDAP_USE_LDAPS = use_ldaps
47 45 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
48 46 self.LDAP_SERVER_ADDRESS = server
49 47 self.LDAP_SERVER_PORT = port
50 48
51 49 #USE FOR READ ONLY BIND TO LDAP SERVER
52 50 self.LDAP_BIND_DN = bind_dn
53 51 self.LDAP_BIND_PASS = bind_pass
54 52
55 53 ldap_server_type = 'ldap'
56 54 if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
57 55 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
58 56 self.LDAP_SERVER_ADDRESS,
59 57 self.LDAP_SERVER_PORT)
60 58
61 59 self.BASE_DN = base_dn
62 60 self.LDAP_FILTER = ldap_filter
63 61 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
64 62 self.attr_login = attr_login
65 63
66 64
67 65 def authenticate_ldap(self, username, password):
68 66 """Authenticate a user via LDAP and return his/her LDAP properties.
69 67
70 68 Raises AuthenticationError if the credentials are rejected, or
71 69 EnvironmentError if the LDAP server can't be reached.
72 70
73 71 :param username: username
74 72 :param password: password
75 73 """
76 74
77 75 from rhodecode.lib.helpers import chop_at
78 76
79 77 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
80 78
81 79 if "," in username:
82 80 raise LdapUsernameError("invalid character in username: ,")
83 81 try:
84 82 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
85 83 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
86 84 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
87 85 ldap.set_option(ldap.OPT_TIMEOUT, 20)
88 86 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
89 87 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
90 88 if self.LDAP_USE_LDAPS:
91 89 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
92 90 server = ldap.initialize(self.LDAP_SERVER)
93 91 if self.ldap_version == 2:
94 92 server.protocol = ldap.VERSION2
95 93 else:
96 94 server.protocol = ldap.VERSION3
97 95
98 96 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
99 97 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
100 98
101 99 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, username)
102 100 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
103 101 filt, self.LDAP_SERVER)
104 102 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
105 103 filt)
106 104
107 105 if not lobjects:
108 106 raise ldap.NO_SUCH_OBJECT()
109 107
110 108 for (dn, attrs) in lobjects:
111 109 try:
112 110 server.simple_bind_s(dn, password)
113 111 break
114 112
115 113 except ldap.INVALID_CREDENTIALS, e:
116 114 log.debug("LDAP rejected password for user '%s' (%s): %s",
117 115 uid, username, dn)
118 116
119 117 else:
120 118 log.debug("No matching LDAP objects for authentication "
121 119 "of '%s' (%s)", uid, username)
122 120 raise LdapPasswordError()
123 121
124 122 except ldap.NO_SUCH_OBJECT, e:
125 123 log.debug("LDAP says no such user '%s' (%s)", uid, username)
126 124 raise LdapUsernameError()
127 125 except ldap.SERVER_DOWN, e:
128 126 raise LdapConnectionError("LDAP can't access authentication server")
129 127
130 128 return (dn, attrs)
@@ -1,106 +1,104 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # mercurial repository backup manager
4 4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 18
21 19 """
22 20 Created on Feb 28, 2010
23 21 Mercurial repositories backup manager
24 22 @author: marcink
25 23 """
26 24
27 25
28 26 import logging
29 27 import tarfile
30 28 import os
31 29 import datetime
32 30 import sys
33 31 import subprocess
34 32 logging.basicConfig(level=logging.DEBUG,
35 33 format="%(asctime)s %(levelname)-5.5s %(message)s")
36 34
37 35 class BackupManager(object):
38 36 def __init__(self, repos_location, rsa_key, backup_server):
39 37 today = datetime.datetime.now().weekday() + 1
40 38 self.backup_file_name = "mercurial_repos.%s.tar.gz" % today
41 39
42 40 self.id_rsa_path = self.get_id_rsa(rsa_key)
43 41 self.repos_path = self.get_repos_path(repos_location)
44 42 self.backup_server = backup_server
45 43
46 44 self.backup_file_path = '/tmp'
47 45
48 46 logging.info('starting backup for %s', self.repos_path)
49 47 logging.info('backup target %s', self.backup_file_path)
50 48
51 49
52 50 def get_id_rsa(self, rsa_key):
53 51 if not os.path.isfile(rsa_key):
54 52 logging.error('Could not load id_rsa key file in %s', rsa_key)
55 53 sys.exit()
56 54 return rsa_key
57 55
58 56 def get_repos_path(self, path):
59 57 if not os.path.isdir(path):
60 58 logging.error('Wrong location for repositories in %s', path)
61 59 sys.exit()
62 60 return path
63 61
64 62 def backup_repos(self):
65 63 bckp_file = os.path.join(self.backup_file_path, self.backup_file_name)
66 64 tar = tarfile.open(bckp_file, "w:gz")
67 65
68 66 for dir_name in os.listdir(self.repos_path):
69 67 logging.info('backing up %s', dir_name)
70 68 tar.add(os.path.join(self.repos_path, dir_name), dir_name)
71 69 tar.close()
72 70 logging.info('finished backup of mercurial repositories')
73 71
74 72
75 73
76 74 def transfer_files(self):
77 75 params = {
78 76 'id_rsa_key': self.id_rsa_path,
79 77 'backup_file':os.path.join(self.backup_file_path,
80 78 self.backup_file_name),
81 79 'backup_server':self.backup_server
82 80 }
83 81 cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params,
84 82 '%(backup_file)s' % params,
85 83 '%(backup_server)s' % params]
86 84
87 85 subprocess.call(cmd)
88 86 logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
89 87
90 88
91 89 def rm_file(self):
92 90 logging.info('Removing file %s', self.backup_file_name)
93 91 os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
94 92
95 93
96 94
97 95 if __name__ == "__main__":
98 96
99 97 repo_location = '/home/repo_path'
100 98 backup_server = 'root@192.168.1.100:/backups/mercurial'
101 99 rsa_key = '/home/id_rsa'
102 100
103 101 B_MANAGER = BackupManager(repo_location, rsa_key, backup_server)
104 102 B_MANAGER.backup_repos()
105 103 B_MANAGER.transfer_files()
106 104 B_MANAGER.rm_file()
@@ -1,99 +1,97 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 celery libs for RhodeCode
7 7
8 8 :created_on: Nov 27, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import os
29 27 import sys
30 28 import socket
31 29 import traceback
32 30 import logging
33 31
34 32 from hashlib import md5
35 33 from decorator import decorator
36 34 from pylons import config
37 35
38 36 from vcs.utils.lazy import LazyProperty
39 37
40 38 from rhodecode.lib import str2bool
41 39 from rhodecode.lib.pidlock import DaemonLock, LockHeld
42 40
43 41 from celery.messaging import establish_connection
44 42
45 43
46 44 log = logging.getLogger(__name__)
47 45
48 46 try:
49 47 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
50 48 except KeyError:
51 49 CELERY_ON = False
52 50
53 51 class ResultWrapper(object):
54 52 def __init__(self, task):
55 53 self.task = task
56 54
57 55 @LazyProperty
58 56 def result(self):
59 57 return self.task
60 58
61 59 def run_task(task, *args, **kwargs):
62 60 if CELERY_ON:
63 61 try:
64 62 t = task.apply_async(args=args, kwargs=kwargs)
65 63 log.info('running task %s:%s', t.task_id, task)
66 64 return t
67 65 except socket.error, e:
68 66 if e.errno == 111:
69 67 log.debug('Unable to connect to celeryd. Sync execution')
70 68 else:
71 69 log.error(traceback.format_exc())
72 70 except KeyError, e:
73 71 log.debug('Unable to connect to celeryd. Sync execution')
74 72 except Exception, e:
75 73 log.error(traceback.format_exc())
76 74
77 75 log.debug('executing task %s in sync mode', task)
78 76 return ResultWrapper(task(*args, **kwargs))
79 77
80 78
81 79 def locked_task(func):
82 80 def __wrapper(func, *fargs, **fkwargs):
83 81 params = list(fargs)
84 82 params.extend(['%s-%s' % ar for ar in fkwargs.items()])
85 83
86 84 lockkey = 'task_%s' % \
87 85 md5(str(func.__name__) + '-' + \
88 86 '-'.join(map(str, params))).hexdigest()
89 87 log.info('running task with lockkey %s', lockkey)
90 88 try:
91 89 l = DaemonLock(lockkey)
92 90 ret = func(*fargs, **fkwargs)
93 91 l.release()
94 92 return ret
95 93 except LockHeld:
96 94 log.info('LockHeld')
97 95 return 'Task with key %s already running' % lockkey
98 96
99 97 return decorator(__wrapper, func)
@@ -1,406 +1,404 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.tasks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode task modules, containing all task that suppose to be run
7 7 by celery daemon
8 8
9 9 :created_on: Oct 6, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 26 from celery.decorators import task
29 27
30 28 import os
31 29 import traceback
32 30 import logging
33 31
34 32 from time import mktime
35 33 from operator import itemgetter
36 34
37 35 from pylons import config
38 36 from pylons.i18n.translation import _
39 37
40 38 from rhodecode.lib.celerylib import run_task, locked_task, str2bool
41 39 from rhodecode.lib.helpers import person
42 40 from rhodecode.lib.smtp_mailer import SmtpMailer
43 41 from rhodecode.lib.utils import OrderedDict, add_cache
44 42 from rhodecode.model import init_model
45 43 from rhodecode.model import meta
46 44 from rhodecode.model.db import RhodeCodeUi
47 45
48 46 from vcs.backends import get_repo
49 47
50 48 from sqlalchemy import engine_from_config
51 49
52 50 add_cache(config)
53 51
54 52 try:
55 53 import json
56 54 except ImportError:
57 55 #python 2.5 compatibility
58 56 import simplejson as json
59 57
60 58 __all__ = ['whoosh_index', 'get_commits_stats',
61 59 'reset_user_password', 'send_email']
62 60
63 61 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
64 62
65 63 def get_session():
66 64 if CELERY_ON:
67 65 engine = engine_from_config(config, 'sqlalchemy.db1.')
68 66 init_model(engine)
69 67 sa = meta.Session()
70 68 return sa
71 69
72 70 def get_repos_path():
73 71 sa = get_session()
74 72 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
75 73 return q.ui_value
76 74
77 75 @task(ignore_result=True)
78 76 @locked_task
79 77 def whoosh_index(repo_location, full_index):
80 78 #log = whoosh_index.get_logger()
81 79 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
82 80 index_location = config['index_dir']
83 81 WhooshIndexingDaemon(index_location=index_location,
84 82 repo_location=repo_location, sa=get_session())\
85 83 .run(full_index=full_index)
86 84
87 85 @task(ignore_result=True)
88 86 @locked_task
89 87 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
90 88 try:
91 89 log = get_commits_stats.get_logger()
92 90 except:
93 91 log = logging.getLogger(__name__)
94 92
95 93 from rhodecode.model.db import Statistics, Repository
96 94
97 95 #for js data compatibilty
98 96 author_key_cleaner = lambda k: person(k).replace('"', "")
99 97
100 98 commits_by_day_author_aggregate = {}
101 99 commits_by_day_aggregate = {}
102 100 repos_path = get_repos_path()
103 101 p = os.path.join(repos_path, repo_name)
104 102 repo = get_repo(p)
105 103
106 104 skip_date_limit = True
107 105 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
108 106 last_rev = 0
109 107 last_cs = None
110 108 timegetter = itemgetter('time')
111 109
112 110 sa = get_session()
113 111
114 112 dbrepo = sa.query(Repository)\
115 113 .filter(Repository.repo_name == repo_name).scalar()
116 114 cur_stats = sa.query(Statistics)\
117 115 .filter(Statistics.repository == dbrepo).scalar()
118 116
119 117 if cur_stats is not None:
120 118 last_rev = cur_stats.stat_on_revision
121 119
122 120 #return if repo is empty
123 121 if not repo.revisions:
124 122 return True
125 123
126 124 if last_rev == repo.get_changeset().revision and len(repo.revisions) > 1:
127 125 #pass silently without any work if we're not on first revision or
128 126 #current state of parsing revision(from db marker) is the last revision
129 127 return True
130 128
131 129 if cur_stats:
132 130 commits_by_day_aggregate = OrderedDict(
133 131 json.loads(
134 132 cur_stats.commit_activity_combined))
135 133 commits_by_day_author_aggregate = json.loads(cur_stats.commit_activity)
136 134
137 135 log.debug('starting parsing %s', parse_limit)
138 136 lmktime = mktime
139 137
140 138 last_rev = last_rev + 1 if last_rev > 0 else last_rev
141 139
142 140 for cs in repo[last_rev:last_rev + parse_limit]:
143 141 last_cs = cs #remember last parsed changeset
144 142 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
145 143 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
146 144
147 145 if commits_by_day_author_aggregate.has_key(author_key_cleaner(cs.author)):
148 146 try:
149 147 l = [timegetter(x) for x in commits_by_day_author_aggregate\
150 148 [author_key_cleaner(cs.author)]['data']]
151 149 time_pos = l.index(k)
152 150 except ValueError:
153 151 time_pos = False
154 152
155 153 if time_pos >= 0 and time_pos is not False:
156 154
157 155 datadict = commits_by_day_author_aggregate\
158 156 [author_key_cleaner(cs.author)]['data'][time_pos]
159 157
160 158 datadict["commits"] += 1
161 159 datadict["added"] += len(cs.added)
162 160 datadict["changed"] += len(cs.changed)
163 161 datadict["removed"] += len(cs.removed)
164 162
165 163 else:
166 164 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
167 165
168 166 datadict = {"time":k,
169 167 "commits":1,
170 168 "added":len(cs.added),
171 169 "changed":len(cs.changed),
172 170 "removed":len(cs.removed),
173 171 }
174 172 commits_by_day_author_aggregate\
175 173 [author_key_cleaner(cs.author)]['data'].append(datadict)
176 174
177 175 else:
178 176 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
179 177 commits_by_day_author_aggregate[author_key_cleaner(cs.author)] = {
180 178 "label":author_key_cleaner(cs.author),
181 179 "data":[{"time":k,
182 180 "commits":1,
183 181 "added":len(cs.added),
184 182 "changed":len(cs.changed),
185 183 "removed":len(cs.removed),
186 184 }],
187 185 "schema":["commits"],
188 186 }
189 187
190 188 #gather all data by day
191 189 if commits_by_day_aggregate.has_key(k):
192 190 commits_by_day_aggregate[k] += 1
193 191 else:
194 192 commits_by_day_aggregate[k] = 1
195 193
196 194 overview_data = sorted(commits_by_day_aggregate.items(), key=itemgetter(0))
197 195 if not commits_by_day_author_aggregate:
198 196 commits_by_day_author_aggregate[author_key_cleaner(repo.contact)] = {
199 197 "label":author_key_cleaner(repo.contact),
200 198 "data":[0, 1],
201 199 "schema":["commits"],
202 200 }
203 201
204 202 stats = cur_stats if cur_stats else Statistics()
205 203 stats.commit_activity = json.dumps(commits_by_day_author_aggregate)
206 204 stats.commit_activity_combined = json.dumps(overview_data)
207 205
208 206 log.debug('last revison %s', last_rev)
209 207 leftovers = len(repo.revisions[last_rev:])
210 208 log.debug('revisions to parse %s', leftovers)
211 209
212 210 if last_rev == 0 or leftovers < parse_limit:
213 211 log.debug('getting code trending stats')
214 212 stats.languages = json.dumps(__get_codes_stats(repo_name))
215 213
216 214 try:
217 215 stats.repository = dbrepo
218 216 stats.stat_on_revision = last_cs.revision if last_cs else 0
219 217 sa.add(stats)
220 218 sa.commit()
221 219 except:
222 220 log.error(traceback.format_exc())
223 221 sa.rollback()
224 222 return False
225 223 if len(repo.revisions) > 1:
226 224 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
227 225
228 226 return True
229 227
230 228 @task(ignore_result=True)
231 229 def reset_user_password(user_email):
232 230 try:
233 231 log = reset_user_password.get_logger()
234 232 except:
235 233 log = logging.getLogger(__name__)
236 234
237 235 from rhodecode.lib import auth
238 236 from rhodecode.model.db import User
239 237
240 238 try:
241 239 try:
242 240 sa = get_session()
243 241 user = sa.query(User).filter(User.email == user_email).scalar()
244 242 new_passwd = auth.PasswordGenerator().gen_password(8,
245 243 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
246 244 if user:
247 245 user.password = auth.get_crypt_password(new_passwd)
248 246 user.api_key = auth.generate_api_key(user.username)
249 247 sa.add(user)
250 248 sa.commit()
251 249 log.info('change password for %s', user_email)
252 250 if new_passwd is None:
253 251 raise Exception('unable to generate new password')
254 252
255 253 except:
256 254 log.error(traceback.format_exc())
257 255 sa.rollback()
258 256
259 257 run_task(send_email, user_email,
260 258 "Your new rhodecode password",
261 259 'Your new rhodecode password:%s' % (new_passwd))
262 260 log.info('send new password mail to %s', user_email)
263 261
264 262
265 263 except:
266 264 log.error('Failed to update user password')
267 265 log.error(traceback.format_exc())
268 266
269 267 return True
270 268
271 269 @task(ignore_result=True)
272 270 def send_email(recipients, subject, body):
273 271 """
274 272 Sends an email with defined parameters from the .ini files.
275 273
276 274
277 275 :param recipients: list of recipients, it this is empty the defined email
278 276 address from field 'email_to' is used instead
279 277 :param subject: subject of the mail
280 278 :param body: body of the mail
281 279 """
282 280 try:
283 281 log = send_email.get_logger()
284 282 except:
285 283 log = logging.getLogger(__name__)
286 284
287 285 email_config = config
288 286
289 287 if not recipients:
290 288 recipients = [email_config.get('email_to')]
291 289
292 290 mail_from = email_config.get('app_email_from')
293 291 user = email_config.get('smtp_username')
294 292 passwd = email_config.get('smtp_password')
295 293 mail_server = email_config.get('smtp_server')
296 294 mail_port = email_config.get('smtp_port')
297 295 tls = str2bool(email_config.get('smtp_use_tls'))
298 296 ssl = str2bool(email_config.get('smtp_use_ssl'))
299 297 debug = str2bool(config.get('debug'))
300 298
301 299 try:
302 300 m = SmtpMailer(mail_from, user, passwd, mail_server,
303 301 mail_port, ssl, tls, debug=debug)
304 302 m.send(recipients, subject, body)
305 303 except:
306 304 log.error('Mail sending failed')
307 305 log.error(traceback.format_exc())
308 306 return False
309 307 return True
310 308
311 309 @task(ignore_result=True)
312 310 def create_repo_fork(form_data, cur_user):
313 311 try:
314 312 log = create_repo_fork.get_logger()
315 313 except:
316 314 log = logging.getLogger(__name__)
317 315
318 316 from rhodecode.model.repo import RepoModel
319 317 from vcs import get_backend
320 318
321 319 repo_model = RepoModel(get_session())
322 320 repo_model.create(form_data, cur_user, just_db=True, fork=True)
323 321 repo_name = form_data['repo_name']
324 322 repos_path = get_repos_path()
325 323 repo_path = os.path.join(repos_path, repo_name)
326 324 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
327 325 alias = form_data['repo_type']
328 326
329 327 log.info('creating repo fork %s as %s', repo_name, repo_path)
330 328 backend = get_backend(alias)
331 329 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
332 330
333 331 def __get_codes_stats(repo_name):
334 332 LANGUAGES_EXTENSIONS_MAP = {'scm': 'Scheme', 'asmx': 'VbNetAspx', 'Rout':
335 333 'RConsole', 'rest': 'Rst', 'abap': 'ABAP', 'go': 'Go', 'phtml': 'HtmlPhp',
336 334 'ns2': 'Newspeak', 'xml': 'EvoqueXml', 'sh-session': 'BashSession', 'ads':
337 335 'Ada', 'clj': 'Clojure', 'll': 'Llvm', 'ebuild': 'Bash', 'adb': 'Ada',
338 336 'ada': 'Ada', 'c++-objdump': 'CppObjdump', 'aspx':
339 337 'VbNetAspx', 'ksh': 'Bash', 'coffee': 'CoffeeScript', 'vert': 'GLShader',
340 338 'Makefile.*': 'Makefile', 'di': 'D', 'dpatch': 'DarcsPatch', 'rake':
341 339 'Ruby', 'moo': 'MOOCode', 'erl-sh': 'ErlangShell', 'geo': 'GLShader',
342 340 'pov': 'Povray', 'bas': 'VbNet', 'bat': 'Batch', 'd': 'D', 'lisp':
343 341 'CommonLisp', 'h': 'C', 'rbx': 'Ruby', 'tcl': 'Tcl', 'c++': 'Cpp', 'md':
344 342 'MiniD', '.vimrc': 'Vim', 'xsd': 'Xml', 'ml': 'Ocaml', 'el': 'CommonLisp',
345 343 'befunge': 'Befunge', 'xsl': 'Xslt', 'pyx': 'Cython', 'cfm':
346 344 'ColdfusionHtml', 'evoque': 'Evoque', 'cfg': 'Ini', 'htm': 'Html',
347 345 'Makefile': 'Makefile', 'cfc': 'ColdfusionHtml', 'tex': 'Tex', 'cs':
348 346 'CSharp', 'mxml': 'Mxml', 'patch': 'Diff', 'apache.conf': 'ApacheConf',
349 347 'scala': 'Scala', 'applescript': 'AppleScript', 'GNUmakefile': 'Makefile',
350 348 'c-objdump': 'CObjdump', 'lua': 'Lua', 'apache2.conf': 'ApacheConf', 'rb':
351 349 'Ruby', 'gemspec': 'Ruby', 'rl': 'RagelObjectiveC', 'vala': 'Vala', 'tmpl':
352 350 'Cheetah', 'bf': 'Brainfuck', 'plt': 'Gnuplot', 'G': 'AntlrRuby', 'xslt':
353 351 'Xslt', 'flxh': 'Felix', 'asax': 'VbNetAspx', 'Rakefile': 'Ruby', 'S': 'S',
354 352 'wsdl': 'Xml', 'js': 'Javascript', 'autodelegate': 'Myghty', 'properties':
355 353 'Ini', 'bash': 'Bash', 'c': 'C', 'g': 'AntlrRuby', 'r3': 'Rebol', 's':
356 354 'Gas', 'ashx': 'VbNetAspx', 'cxx': 'Cpp', 'boo': 'Boo', 'prolog': 'Prolog',
357 355 'sqlite3-console': 'SqliteConsole', 'cl': 'CommonLisp', 'cc': 'Cpp', 'pot':
358 356 'Gettext', 'vim': 'Vim', 'pxi': 'Cython', 'yaml': 'Yaml', 'SConstruct':
359 357 'Python', 'diff': 'Diff', 'txt': 'Text', 'cw': 'Redcode', 'pxd': 'Cython',
360 358 'plot': 'Gnuplot', 'java': 'Java', 'hrl': 'Erlang', 'py': 'Python',
361 359 'makefile': 'Makefile', 'squid.conf': 'SquidConf', 'asm': 'Nasm', 'toc':
362 360 'Tex', 'kid': 'Genshi', 'rhtml': 'Rhtml', 'po': 'Gettext', 'pl': 'Prolog',
363 361 'pm': 'Perl', 'hx': 'Haxe', 'ascx': 'VbNetAspx', 'ooc': 'Ooc', 'asy':
364 362 'Asymptote', 'hs': 'Haskell', 'SConscript': 'Python', 'pytb':
365 363 'PythonTraceback', 'myt': 'Myghty', 'hh': 'Cpp', 'R': 'S', 'aux': 'Tex',
366 364 'rst': 'Rst', 'cpp-objdump': 'CppObjdump', 'lgt': 'Logtalk', 'rss': 'Xml',
367 365 'flx': 'Felix', 'b': 'Brainfuck', 'f': 'Fortran', 'rbw': 'Ruby',
368 366 '.htaccess': 'ApacheConf', 'cxx-objdump': 'CppObjdump', 'j': 'ObjectiveJ',
369 367 'mll': 'Ocaml', 'yml': 'Yaml', 'mu': 'MuPAD', 'r': 'Rebol', 'ASM': 'Nasm',
370 368 'erl': 'Erlang', 'mly': 'Ocaml', 'mo': 'Modelica', 'def': 'Modula2', 'ini':
371 369 'Ini', 'control': 'DebianControl', 'vb': 'VbNet', 'vapi': 'Vala', 'pro':
372 370 'Prolog', 'spt': 'Cheetah', 'mli': 'Ocaml', 'as': 'ActionScript3', 'cmd':
373 371 'Batch', 'cpp': 'Cpp', 'io': 'Io', 'tac': 'Python', 'haml': 'Haml', 'rkt':
374 372 'Racket', 'st':'Smalltalk', 'inc': 'Povray', 'pas': 'Delphi', 'cmake':
375 373 'CMake', 'csh':'Tcsh', 'hpp': 'Cpp', 'feature': 'Gherkin', 'html': 'Html',
376 374 'php':'Php', 'php3':'Php', 'php4':'Php', 'php5':'Php', 'xhtml': 'Html',
377 375 'hxx': 'Cpp', 'eclass': 'Bash', 'css': 'Css',
378 376 'frag': 'GLShader', 'd-objdump': 'DObjdump', 'weechatlog': 'IrcLogs',
379 377 'tcsh': 'Tcsh', 'objdump': 'Objdump', 'pyw': 'Python', 'h++': 'Cpp',
380 378 'py3tb': 'Python3Traceback', 'jsp': 'Jsp', 'sql': 'Sql', 'mak': 'Makefile',
381 379 'php': 'Php', 'mao': 'Mako', 'man': 'Groff', 'dylan': 'Dylan', 'sass':
382 380 'Sass', 'cfml': 'ColdfusionHtml', 'darcspatch': 'DarcsPatch', 'tpl':
383 381 'Smarty', 'm': 'ObjectiveC', 'f90': 'Fortran', 'mod': 'Modula2', 'sh':
384 382 'Bash', 'lhs': 'LiterateHaskell', 'sources.list': 'SourcesList', 'axd':
385 383 'VbNetAspx', 'sc': 'Python'}
386 384
387 385 repos_path = get_repos_path()
388 386 p = os.path.join(repos_path, repo_name)
389 387 repo = get_repo(p)
390 388 tip = repo.get_changeset()
391 389 code_stats = {}
392 390
393 391 def aggregate(cs):
394 392 for f in cs[2]:
395 393 ext = f.extension
396 394 key = LANGUAGES_EXTENSIONS_MAP.get(ext, ext)
397 395 key = key or ext
398 396 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
399 397 if code_stats.has_key(key):
400 398 code_stats[key] += 1
401 399 else:
402 400 code_stats[key] = 1
403 401
404 402 map(aggregate, tip.walk('/'))
405 403
406 404 return code_stats or {}
@@ -1,518 +1,516 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 26
29 27 import os
30 28 import sys
31 29 import uuid
32 30 import logging
33 31 from os.path import dirname as dn, join as jn
34 32
35 33 from rhodecode import __dbversion__
36 34 from rhodecode.model import meta
37 35
38 36 from rhodecode.lib.auth import get_crypt_password, generate_api_key
39 37 from rhodecode.lib.utils import ask_ok
40 38 from rhodecode.model import init_model
41 39 from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \
42 40 UserToPerm, DbMigrateVersion
43 41
44 42 from sqlalchemy.engine import create_engine
45 43
46 44 log = logging.getLogger(__name__)
47 45
48 46 class DbManage(object):
49 47 def __init__(self, log_sql, dbconf, root, tests=False):
50 48 self.dbname = dbconf.split('/')[-1]
51 49 self.tests = tests
52 50 self.root = root
53 51 self.dburi = dbconf
54 52 self.log_sql = log_sql
55 53 self.db_exists = False
56 54 self.init_db()
57 55
58 56 def init_db(self):
59 57 engine = create_engine(self.dburi, echo=self.log_sql)
60 58 init_model(engine)
61 59 self.sa = meta.Session()
62 60
63 61 def create_tables(self, override=False):
64 62 """Create a auth database
65 63 """
66 64
67 65 log.info("Any existing database is going to be destroyed")
68 66 if self.tests:
69 67 destroy = True
70 68 else:
71 69 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
72 70 if not destroy:
73 71 sys.exit()
74 72 if destroy:
75 73 meta.Base.metadata.drop_all()
76 74
77 75 checkfirst = not override
78 76 meta.Base.metadata.create_all(checkfirst=checkfirst)
79 77 log.info('Created tables for %s', self.dbname)
80 78
81 79
82 80
83 81 def set_db_version(self):
84 82 try:
85 83 ver = DbMigrateVersion()
86 84 ver.version = __dbversion__
87 85 ver.repository_id = 'rhodecode_db_migrations'
88 86 ver.repository_path = 'versions'
89 87 self.sa.add(ver)
90 88 self.sa.commit()
91 89 except:
92 90 self.sa.rollback()
93 91 raise
94 92 log.info('db version set to: %s', __dbversion__)
95 93
96 94
97 95 def upgrade(self):
98 96 """Upgrades given database schema to given revision following
99 97 all needed steps, to perform the upgrade
100 98
101 99 """
102 100
103 101 from rhodecode.lib.dbmigrate.migrate.versioning import api
104 102 from rhodecode.lib.dbmigrate.migrate.exceptions import \
105 103 DatabaseNotControlledError
106 104
107 105 upgrade = ask_ok('You are about to perform database upgrade, make '
108 106 'sure You backed up your database before. '
109 107 'Continue ? [y/n]')
110 108 if not upgrade:
111 109 sys.exit('Nothing done')
112 110
113 111 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
114 112 'rhodecode/lib/dbmigrate')
115 113 db_uri = self.dburi
116 114
117 115 try:
118 116 curr_version = api.db_version(db_uri, repository_path)
119 117 msg = ('Found current database under version'
120 118 ' control with version %s' % curr_version)
121 119
122 120 except (RuntimeError, DatabaseNotControlledError), e:
123 121 curr_version = 1
124 122 msg = ('Current database is not under version control. Setting'
125 123 ' as version %s' % curr_version)
126 124 api.version_control(db_uri, repository_path, curr_version)
127 125
128 126 print (msg)
129 127
130 128 if curr_version == __dbversion__:
131 129 sys.exit('This database is already at the newest version')
132 130
133 131 #======================================================================
134 132 # UPGRADE STEPS
135 133 #======================================================================
136 134 class UpgradeSteps(object):
137 135 """Those steps follow schema versions so for example schema
138 136 for example schema with seq 002 == step_2 and so on.
139 137 """
140 138
141 139 def __init__(self, klass):
142 140 self.klass = klass
143 141
144 142 def step_0(self):
145 143 #step 0 is the schema upgrade, and than follow proper upgrades
146 144 print ('attempting to do database upgrade to version %s' \
147 145 % __dbversion__)
148 146 api.upgrade(db_uri, repository_path, __dbversion__)
149 147 print ('Schema upgrade completed')
150 148
151 149 def step_1(self):
152 150 pass
153 151
154 152 def step_2(self):
155 153 print ('Patching repo paths for newer version of RhodeCode')
156 154 self.klass.fix_repo_paths()
157 155
158 156 print ('Patching default user of RhodeCode')
159 157 self.klass.fix_default_user()
160 158
161 159 log.info('Changing ui settings')
162 160 self.klass.create_ui_settings()
163 161
164 162 def step_3(self):
165 163 print ('Adding additional settings into RhodeCode db')
166 164 self.klass.fix_settings()
167 165
168 166 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
169 167
170 168 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
171 169 for step in upgrade_steps:
172 170 print ('performing upgrade step %s' % step)
173 171 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
174 172
175 173
176 174
177 175 def fix_repo_paths(self):
178 176 """Fixes a old rhodecode version path into new one without a '*'
179 177 """
180 178
181 179 paths = self.sa.query(RhodeCodeUi)\
182 180 .filter(RhodeCodeUi.ui_key == '/')\
183 181 .scalar()
184 182
185 183 paths.ui_value = paths.ui_value.replace('*', '')
186 184
187 185 try:
188 186 self.sa.add(paths)
189 187 self.sa.commit()
190 188 except:
191 189 self.sa.rollback()
192 190 raise
193 191
194 192 def fix_default_user(self):
195 193 """Fixes a old default user with some 'nicer' default values,
196 194 used mostly for anonymous access
197 195 """
198 196 def_user = self.sa.query(User)\
199 197 .filter(User.username == 'default')\
200 198 .one()
201 199
202 200 def_user.name = 'Anonymous'
203 201 def_user.lastname = 'User'
204 202 def_user.email = 'anonymous@rhodecode.org'
205 203
206 204 try:
207 205 self.sa.add(def_user)
208 206 self.sa.commit()
209 207 except:
210 208 self.sa.rollback()
211 209 raise
212 210
213 211 def fix_settings(self):
214 212 """Fixes rhodecode settings adds ga_code key for google analytics
215 213 """
216 214
217 215 hgsettings3 = RhodeCodeSettings('ga_code', '')
218 216
219 217 try:
220 218 self.sa.add(hgsettings3)
221 219 self.sa.commit()
222 220 except:
223 221 self.sa.rollback()
224 222 raise
225 223
226 224 def admin_prompt(self, second=False):
227 225 if not self.tests:
228 226 import getpass
229 227
230 228
231 229 def get_password():
232 230 password = getpass.getpass('Specify admin password (min 6 chars):')
233 231 confirm = getpass.getpass('Confirm password:')
234 232
235 233 if password != confirm:
236 234 log.error('passwords mismatch')
237 235 return False
238 236 if len(password) < 6:
239 237 log.error('password is to short use at least 6 characters')
240 238 return False
241 239
242 240 return password
243 241
244 242 username = raw_input('Specify admin username:')
245 243
246 244 password = get_password()
247 245 if not password:
248 246 #second try
249 247 password = get_password()
250 248 if not password:
251 249 sys.exit()
252 250
253 251 email = raw_input('Specify admin email:')
254 252 self.create_user(username, password, email, True)
255 253 else:
256 254 log.info('creating admin and regular test users')
257 255 self.create_user('test_admin', 'test12', 'test_admin@mail.com', True)
258 256 self.create_user('test_regular', 'test12', 'test_regular@mail.com', False)
259 257 self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False)
260 258
261 259 def create_ui_settings(self):
262 260 """Creates ui settings, fills out hooks
263 261 and disables dotencode
264 262
265 263 """
266 264 #HOOKS
267 265 hooks1_key = 'changegroup.update'
268 266 hooks1_ = self.sa.query(RhodeCodeUi)\
269 267 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
270 268
271 269 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
272 270 hooks1.ui_section = 'hooks'
273 271 hooks1.ui_key = hooks1_key
274 272 hooks1.ui_value = 'hg update >&2'
275 273 hooks1.ui_active = False
276 274
277 275 hooks2_key = 'changegroup.repo_size'
278 276 hooks2_ = self.sa.query(RhodeCodeUi)\
279 277 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
280 278
281 279 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
282 280 hooks2.ui_section = 'hooks'
283 281 hooks2.ui_key = hooks2_key
284 282 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
285 283
286 284 hooks3 = RhodeCodeUi()
287 285 hooks3.ui_section = 'hooks'
288 286 hooks3.ui_key = 'pretxnchangegroup.push_logger'
289 287 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
290 288
291 289 hooks4 = RhodeCodeUi()
292 290 hooks4.ui_section = 'hooks'
293 291 hooks4.ui_key = 'preoutgoing.pull_logger'
294 292 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
295 293
296 294 #For mercurial 1.7 set backward comapatibility with format
297 295 dotencode_disable = RhodeCodeUi()
298 296 dotencode_disable.ui_section = 'format'
299 297 dotencode_disable.ui_key = 'dotencode'
300 298 dotencode_disable.ui_value = 'false'
301 299
302 300 try:
303 301 self.sa.add(hooks1)
304 302 self.sa.add(hooks2)
305 303 self.sa.add(hooks3)
306 304 self.sa.add(hooks4)
307 305 self.sa.add(dotencode_disable)
308 306 self.sa.commit()
309 307 except:
310 308 self.sa.rollback()
311 309 raise
312 310
313 311
314 312 def create_ldap_options(self):
315 313 """Creates ldap settings"""
316 314
317 315 try:
318 316 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
319 317 ('ldap_port', '389'), ('ldap_ldaps', 'false'),
320 318 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
321 319 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
322 320 ('ldap_filter', ''), ('ldap_search_scope', ''),
323 321 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
324 322 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
325 323
326 324
327 325 setting = RhodeCodeSettings(k, v)
328 326 self.sa.add(setting)
329 327 self.sa.commit()
330 328 except:
331 329 self.sa.rollback()
332 330 raise
333 331
334 332 def config_prompt(self, test_repo_path='', retries=3):
335 333 if retries == 3:
336 334 log.info('Setting up repositories config')
337 335
338 336 if not self.tests and not test_repo_path:
339 337 path = raw_input('Specify valid full path to your repositories'
340 338 ' you can change this later in application settings:')
341 339 else:
342 340 path = test_repo_path
343 341 path_ok = True
344 342
345 343 #check proper dir
346 344 if not os.path.isdir(path):
347 345 path_ok = False
348 346 log.error('Entered path is not a valid directory: %s [%s/3]',
349 347 path, retries)
350 348
351 349 #check write access
352 350 if not os.access(path, os.W_OK):
353 351 path_ok = False
354 352
355 353 log.error('No write permission to given path: %s [%s/3]',
356 354 path, retries)
357 355
358 356
359 357 if retries == 0:
360 358 sys.exit()
361 359 if path_ok is False:
362 360 retries -= 1
363 361 return self.config_prompt(test_repo_path, retries)
364 362
365 363
366 364 return path
367 365
368 366 def create_settings(self, path):
369 367
370 368 self.create_ui_settings()
371 369
372 370 #HG UI OPTIONS
373 371 web1 = RhodeCodeUi()
374 372 web1.ui_section = 'web'
375 373 web1.ui_key = 'push_ssl'
376 374 web1.ui_value = 'false'
377 375
378 376 web2 = RhodeCodeUi()
379 377 web2.ui_section = 'web'
380 378 web2.ui_key = 'allow_archive'
381 379 web2.ui_value = 'gz zip bz2'
382 380
383 381 web3 = RhodeCodeUi()
384 382 web3.ui_section = 'web'
385 383 web3.ui_key = 'allow_push'
386 384 web3.ui_value = '*'
387 385
388 386 web4 = RhodeCodeUi()
389 387 web4.ui_section = 'web'
390 388 web4.ui_key = 'baseurl'
391 389 web4.ui_value = '/'
392 390
393 391 paths = RhodeCodeUi()
394 392 paths.ui_section = 'paths'
395 393 paths.ui_key = '/'
396 394 paths.ui_value = path
397 395
398 396
399 397 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
400 398 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
401 399 hgsettings3 = RhodeCodeSettings('ga_code', '')
402 400
403 401
404 402 try:
405 403 self.sa.add(web1)
406 404 self.sa.add(web2)
407 405 self.sa.add(web3)
408 406 self.sa.add(web4)
409 407 self.sa.add(paths)
410 408 self.sa.add(hgsettings1)
411 409 self.sa.add(hgsettings2)
412 410 self.sa.add(hgsettings3)
413 411
414 412 self.sa.commit()
415 413 except:
416 414 self.sa.rollback()
417 415 raise
418 416
419 417 self.create_ldap_options()
420 418
421 419 log.info('created ui config')
422 420
423 421 def create_user(self, username, password, email='', admin=False):
424 422 log.info('creating administrator user %s', username)
425 423 new_user = User()
426 424 new_user.username = username
427 425 new_user.password = get_crypt_password(password)
428 426 new_user.api_key = generate_api_key(username)
429 427 new_user.name = 'RhodeCode'
430 428 new_user.lastname = 'Admin'
431 429 new_user.email = email
432 430 new_user.admin = admin
433 431 new_user.active = True
434 432
435 433 try:
436 434 self.sa.add(new_user)
437 435 self.sa.commit()
438 436 except:
439 437 self.sa.rollback()
440 438 raise
441 439
442 440 def create_default_user(self):
443 441 log.info('creating default user')
444 442 #create default user for handling default permissions.
445 443 def_user = User()
446 444 def_user.username = 'default'
447 445 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
448 446 def_user.api_key = generate_api_key('default')
449 447 def_user.name = 'Anonymous'
450 448 def_user.lastname = 'User'
451 449 def_user.email = 'anonymous@rhodecode.org'
452 450 def_user.admin = False
453 451 def_user.active = False
454 452 try:
455 453 self.sa.add(def_user)
456 454 self.sa.commit()
457 455 except:
458 456 self.sa.rollback()
459 457 raise
460 458
461 459 def create_permissions(self):
462 460 #module.(access|create|change|delete)_[name]
463 461 #module.(read|write|owner)
464 462 perms = [('repository.none', 'Repository no access'),
465 463 ('repository.read', 'Repository read access'),
466 464 ('repository.write', 'Repository write access'),
467 465 ('repository.admin', 'Repository admin access'),
468 466 ('hg.admin', 'Hg Administrator'),
469 467 ('hg.create.repository', 'Repository create'),
470 468 ('hg.create.none', 'Repository creation disabled'),
471 469 ('hg.register.none', 'Register disabled'),
472 470 ('hg.register.manual_activate', 'Register new user with RhodeCode without manual activation'),
473 471 ('hg.register.auto_activate', 'Register new user with RhodeCode without auto activation'),
474 472 ]
475 473
476 474 for p in perms:
477 475 new_perm = Permission()
478 476 new_perm.permission_name = p[0]
479 477 new_perm.permission_longname = p[1]
480 478 try:
481 479 self.sa.add(new_perm)
482 480 self.sa.commit()
483 481 except:
484 482 self.sa.rollback()
485 483 raise
486 484
487 485 def populate_default_permissions(self):
488 486 log.info('creating default user permissions')
489 487
490 488 default_user = self.sa.query(User)\
491 489 .filter(User.username == 'default').scalar()
492 490
493 491 reg_perm = UserToPerm()
494 492 reg_perm.user = default_user
495 493 reg_perm.permission = self.sa.query(Permission)\
496 494 .filter(Permission.permission_name == 'hg.register.manual_activate')\
497 495 .scalar()
498 496
499 497 create_repo_perm = UserToPerm()
500 498 create_repo_perm.user = default_user
501 499 create_repo_perm.permission = self.sa.query(Permission)\
502 500 .filter(Permission.permission_name == 'hg.create.repository')\
503 501 .scalar()
504 502
505 503 default_repo_perm = UserToPerm()
506 504 default_repo_perm.user = default_user
507 505 default_repo_perm.permission = self.sa.query(Permission)\
508 506 .filter(Permission.permission_name == 'repository.read')\
509 507 .scalar()
510 508
511 509 try:
512 510 self.sa.add(reg_perm)
513 511 self.sa.add(create_repo_perm)
514 512 self.sa.add(default_repo_perm)
515 513 self.sa.commit()
516 514 except:
517 515 self.sa.rollback()
518 516 raise
@@ -1,69 +1,67 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.dbmigrate.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database migration modules
7 7
8 8 :created_on: Dec 11, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 from sqlalchemy import engine_from_config
30 28
31 29
32 30 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
33 31 from rhodecode.lib.db_manage import DbManage
34 32
35 33 log = logging.getLogger(__name__)
36 34
37 35 class UpgradeDb(BasePasterCommand):
38 36 """Command used for paster to upgrade our database to newer version
39 37 """
40 38
41 39 max_args = 1
42 40 min_args = 1
43 41
44 42 usage = "CONFIG_FILE"
45 43 summary = "Upgrades current db to newer version given configuration file"
46 44 group_name = "RhodeCode"
47 45
48 46 parser = Command.standard_parser(verbose=True)
49 47
50 48 def command(self):
51 49 from pylons import config
52 50
53 51 add_cache(config)
54 52
55 53 db_uri = config['sqlalchemy.db1.url']
56 54
57 55 dbmanage = DbManage(log_sql=True, dbconf=db_uri,
58 56 root=config['here'], tests=False)
59 57
60 58 dbmanage.upgrade()
61 59
62 60
63 61
64 62 def update_parser(self):
65 63 self.parser.add_option('--sql',
66 64 action='store_true',
67 65 dest='just_sql',
68 66 help="Prints upgrade sql for further investigation",
69 67 default=False)
@@ -1,26 +1,24 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.dbmigrate.versions.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Package containing new versions of database models
7 7
8 8 :created_on: Dec 11, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -1,32 +1,30 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Custom Exceptions modules
4 4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 18 """
21 19 Created on Nov 17, 2010
22 20 Custom Exceptions modules
23 21 @author: marcink
24 22 """
25 23
26 24 class LdapUsernameError(Exception):pass
27 25 class LdapPasswordError(Exception):pass
28 26 class LdapConnectionError(Exception):pass
29 27 class LdapImportError(Exception):pass
30 28
31 29 class DefaultUserException(Exception):pass
32 30 class UserOwnsReposException(Exception):pass
@@ -1,115 +1,113 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.hooks
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Hooks runned by rhodecode
7 7
8 8 :created_on: Aug 6, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25 import os
28 26 import sys
29 27 import getpass
30 28
31 29 from mercurial.cmdutil import revrange
32 30 from mercurial.node import nullrev
33 31
34 32 from rhodecode.lib import helpers as h
35 33 from rhodecode.lib.utils import action_logger
36 34
37 35 def repo_size(ui, repo, hooktype=None, **kwargs):
38 36 """Presents size of repository after push
39 37
40 38 :param ui:
41 39 :param repo:
42 40 :param hooktype:
43 41 """
44 42
45 43 if hooktype != 'changegroup':
46 44 return False
47 45 size_hg, size_root = 0, 0
48 46 for path, dirs, files in os.walk(repo.root):
49 47 if path.find('.hg') != -1:
50 48 for f in files:
51 49 try:
52 50 size_hg += os.path.getsize(os.path.join(path, f))
53 51 except OSError:
54 52 pass
55 53 else:
56 54 for f in files:
57 55 try:
58 56 size_root += os.path.getsize(os.path.join(path, f))
59 57 except OSError:
60 58 pass
61 59
62 60 size_hg_f = h.format_byte_size(size_hg)
63 61 size_root_f = h.format_byte_size(size_root)
64 62 size_total_f = h.format_byte_size(size_root + size_hg)
65 63 sys.stdout.write('Repository size .hg:%s repo:%s total:%s\n' \
66 64 % (size_hg_f, size_root_f, size_total_f))
67 65
68 66 def log_pull_action(ui, repo, **kwargs):
69 67 """Logs user last pull action
70 68
71 69 :param ui:
72 70 :param repo:
73 71 """
74 72
75 73 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
76 74 username = extra_params['username']
77 75 repository = extra_params['repository']
78 76 action = 'pull'
79 77
80 78 action_logger(username, action, repository, extra_params['ip'])
81 79
82 80 return 0
83 81
84 82 def log_push_action(ui, repo, **kwargs):
85 83 """Maps user last push action to new changeset id, from mercurial
86 84
87 85 :param ui:
88 86 :param repo:
89 87 """
90 88
91 89 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
92 90 username = extra_params['username']
93 91 repository = extra_params['repository']
94 92 action = extra_params['action'] + ':%s'
95 93 node = kwargs['node']
96 94
97 95 def get_revs(repo, rev_opt):
98 96 if rev_opt:
99 97 revs = revrange(repo, rev_opt)
100 98
101 99 if len(revs) == 0:
102 100 return (nullrev, nullrev)
103 101 return (max(revs), min(revs))
104 102 else:
105 103 return (len(repo) - 1, 0)
106 104
107 105 stop, start = get_revs(repo, [node + ':'])
108 106
109 107 revs = (str(repo[r]) for r in xrange(start, stop + 1))
110 108
111 109 action = action % ','.join(revs)
112 110
113 111 action_logger(username, action, repository, extra_params['ip'])
114 112
115 113 return 0
@@ -1,232 +1,230 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.indexers.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Whoosh indexing module for RhodeCode
7 7
8 8 :created_on: Aug 17, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25 import os
28 26 import sys
29 27 import traceback
30 28 from os.path import dirname as dn, join as jn
31 29
32 30 #to get the rhodecode import
33 31 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
34 32
35 33 from string import strip
36 34
37 35 from rhodecode.model import init_model
38 36 from rhodecode.model.scm import ScmModel
39 37 from rhodecode.config.environment import load_environment
40 38 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
41 39
42 40 from shutil import rmtree
43 41 from webhelpers.html.builder import escape
44 42 from vcs.utils.lazy import LazyProperty
45 43
46 44 from sqlalchemy import engine_from_config
47 45
48 46 from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
49 47 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
50 48 from whoosh.index import create_in, open_dir
51 49 from whoosh.formats import Characters
52 50 from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter
53 51
54 52
55 53 #EXTENSIONS WE WANT TO INDEX CONTENT OFF
56 54 INDEX_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c',
57 55 'cfg', 'cfm', 'cpp', 'cs', 'css', 'diff', 'do', 'el', 'erl',
58 56 'h', 'htm', 'html', 'ini', 'java', 'js', 'jsp', 'jspx', 'lisp',
59 57 'lua', 'm', 'mako', 'ml', 'pas', 'patch', 'php', 'php3',
60 58 'php4', 'phtml', 'pm', 'py', 'rb', 'rst', 's', 'sh', 'sql',
61 59 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml', 'xsl', 'xslt',
62 60 'yaws']
63 61
64 62 #CUSTOM ANALYZER wordsplit + lowercase filter
65 63 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
66 64
67 65
68 66 #INDEX SCHEMA DEFINITION
69 67 SCHEMA = Schema(owner=TEXT(),
70 68 repository=TEXT(stored=True),
71 69 path=TEXT(stored=True),
72 70 content=FieldType(format=Characters(ANALYZER),
73 71 scorable=True, stored=True),
74 72 modtime=STORED(), extension=TEXT(stored=True))
75 73
76 74
77 75 IDX_NAME = 'HG_INDEX'
78 76 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
79 77 FRAGMENTER = SimpleFragmenter(200)
80 78
81 79
82 80 class MakeIndex(BasePasterCommand):
83 81
84 82 max_args = 1
85 83 min_args = 1
86 84
87 85 usage = "CONFIG_FILE"
88 86 summary = "Creates index for full text search given configuration file"
89 87 group_name = "RhodeCode"
90 88 takes_config_file = -1
91 89 parser = Command.standard_parser(verbose=True)
92 90
93 91 def command(self):
94 92
95 93 from pylons import config
96 94 add_cache(config)
97 95 engine = engine_from_config(config, 'sqlalchemy.db1.')
98 96 init_model(engine)
99 97
100 98 index_location = config['index_dir']
101 99 repo_location = self.options.repo_location
102 100 repo_list = map(strip, self.options.repo_list.split(',')) \
103 101 if self.options.repo_list else None
104 102
105 103 #======================================================================
106 104 # WHOOSH DAEMON
107 105 #======================================================================
108 106 from rhodecode.lib.pidlock import LockHeld, DaemonLock
109 107 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
110 108 try:
111 109 l = DaemonLock()
112 110 WhooshIndexingDaemon(index_location=index_location,
113 111 repo_location=repo_location,
114 112 repo_list=repo_list)\
115 113 .run(full_index=self.options.full_index)
116 114 l.release()
117 115 except LockHeld:
118 116 sys.exit(1)
119 117
120 118 def update_parser(self):
121 119 self.parser.add_option('--repo-location',
122 120 action='store',
123 121 dest='repo_location',
124 122 help="Specifies repositories location to index REQUIRED",
125 123 )
126 124 self.parser.add_option('--index-only',
127 125 action='store',
128 126 dest='repo_list',
129 127 help="Specifies a comma separated list of repositores "
130 128 "to build index on OPTIONAL",
131 129 )
132 130 self.parser.add_option('-f',
133 131 action='store_true',
134 132 dest='full_index',
135 133 help="Specifies that index should be made full i.e"
136 134 " destroy old and build from scratch",
137 135 default=False)
138 136
139 137 class ResultWrapper(object):
140 138 def __init__(self, search_type, searcher, matcher, highlight_items):
141 139 self.search_type = search_type
142 140 self.searcher = searcher
143 141 self.matcher = matcher
144 142 self.highlight_items = highlight_items
145 143 self.fragment_size = 200 / 2
146 144
147 145 @LazyProperty
148 146 def doc_ids(self):
149 147 docs_id = []
150 148 while self.matcher.is_active():
151 149 docnum = self.matcher.id()
152 150 chunks = [offsets for offsets in self.get_chunks()]
153 151 docs_id.append([docnum, chunks])
154 152 self.matcher.next()
155 153 return docs_id
156 154
157 155 def __str__(self):
158 156 return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids))
159 157
160 158 def __repr__(self):
161 159 return self.__str__()
162 160
163 161 def __len__(self):
164 162 return len(self.doc_ids)
165 163
166 164 def __iter__(self):
167 165 """
168 166 Allows Iteration over results,and lazy generate content
169 167
170 168 *Requires* implementation of ``__getitem__`` method.
171 169 """
172 170 for docid in self.doc_ids:
173 171 yield self.get_full_content(docid)
174 172
175 173 def __getitem__(self, key):
176 174 """
177 175 Slicing of resultWrapper
178 176 """
179 177 i, j = key.start, key.stop
180 178
181 179 slice = []
182 180 for docid in self.doc_ids[i:j]:
183 181 slice.append(self.get_full_content(docid))
184 182 return slice
185 183
186 184
187 185 def get_full_content(self, docid):
188 186 res = self.searcher.stored_fields(docid[0])
189 187 f_path = res['path'][res['path'].find(res['repository']) \
190 188 + len(res['repository']):].lstrip('/')
191 189
192 190 content_short = self.get_short_content(res, docid[1])
193 191 res.update({'content_short':content_short,
194 192 'content_short_hl':self.highlight(content_short),
195 193 'f_path':f_path})
196 194
197 195 return res
198 196
199 197 def get_short_content(self, res, chunks):
200 198
201 199 return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
202 200
203 201 def get_chunks(self):
204 202 """
205 203 Smart function that implements chunking the content
206 204 but not overlap chunks so it doesn't highlight the same
207 205 close occurrences twice.
208 206 @param matcher:
209 207 @param size:
210 208 """
211 209 memory = [(0, 0)]
212 210 for span in self.matcher.spans():
213 211 start = span.startchar or 0
214 212 end = span.endchar or 0
215 213 start_offseted = max(0, start - self.fragment_size)
216 214 end_offseted = end + self.fragment_size
217 215
218 216 if start_offseted < memory[-1][1]:
219 217 start_offseted = memory[-1][1]
220 218 memory.append((start_offseted, end_offseted,))
221 219 yield (start_offseted, end_offseted,)
222 220
223 221 def highlight(self, content, top=5):
224 222 if self.search_type != 'content':
225 223 return ''
226 224 hl = highlight(escape(content),
227 225 self.highlight_items,
228 226 analyzer=ANALYZER,
229 227 fragmenter=FRAGMENTER,
230 228 formatter=FORMATTER,
231 229 top=top)
232 230 return hl
@@ -1,242 +1,240 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.indexers.daemon
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 A deamon will read from task table and run tasks
7 7
8 8 :created_on: Jan 26, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import os
29 27 import sys
30 28 import logging
31 29 import traceback
32 30
33 31 from shutil import rmtree
34 32 from time import mktime
35 33
36 34 from os.path import dirname as dn
37 35 from os.path import join as jn
38 36
39 37 #to get the rhodecode import
40 38 project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
41 39 sys.path.append(project_path)
42 40
43 41
44 42 from rhodecode.model.scm import ScmModel
45 43 from rhodecode.lib import safe_unicode
46 44 from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME
47 45
48 46 from vcs.exceptions import ChangesetError, RepositoryError
49 47
50 48 from whoosh.index import create_in, open_dir
51 49
52 50
53 51
54 52 log = logging.getLogger('whooshIndexer')
55 53 # create logger
56 54 log.setLevel(logging.DEBUG)
57 55 log.propagate = False
58 56 # create console handler and set level to debug
59 57 ch = logging.StreamHandler()
60 58 ch.setLevel(logging.DEBUG)
61 59
62 60 # create formatter
63 61 formatter = logging.Formatter("%(asctime)s - %(name)s -"
64 62 " %(levelname)s - %(message)s")
65 63
66 64 # add formatter to ch
67 65 ch.setFormatter(formatter)
68 66
69 67 # add ch to logger
70 68 log.addHandler(ch)
71 69
72 70 class WhooshIndexingDaemon(object):
73 71 """
74 72 Deamon for atomic jobs
75 73 """
76 74
77 75 def __init__(self, indexname='HG_INDEX', index_location=None,
78 76 repo_location=None, sa=None, repo_list=None):
79 77 self.indexname = indexname
80 78
81 79 self.index_location = index_location
82 80 if not index_location:
83 81 raise Exception('You have to provide index location')
84 82
85 83 self.repo_location = repo_location
86 84 if not repo_location:
87 85 raise Exception('You have to provide repositories location')
88 86
89 87 self.repo_paths = ScmModel(sa).repo_scan(self.repo_location)
90 88
91 89 if repo_list:
92 90 filtered_repo_paths = {}
93 91 for repo_name, repo in self.repo_paths.items():
94 92 if repo_name in repo_list:
95 93 filtered_repo_paths[repo_name] = repo
96 94
97 95 self.repo_paths = filtered_repo_paths
98 96
99 97
100 98 self.initial = False
101 99 if not os.path.isdir(self.index_location):
102 100 os.makedirs(self.index_location)
103 101 log.info('Cannot run incremental index since it does not'
104 102 ' yet exist running full build')
105 103 self.initial = True
106 104
107 105 def get_paths(self, repo):
108 106 """recursive walk in root dir and return a set of all path in that dir
109 107 based on repository walk function
110 108 """
111 109 index_paths_ = set()
112 110 try:
113 111 tip = repo.get_changeset('tip')
114 112 for topnode, dirs, files in tip.walk('/'):
115 113 for f in files:
116 114 index_paths_.add(jn(repo.path, f.path))
117 115 for dir in dirs:
118 116 for f in files:
119 117 index_paths_.add(jn(repo.path, f.path))
120 118
121 119 except RepositoryError, e:
122 120 log.debug(traceback.format_exc())
123 121 pass
124 122 return index_paths_
125 123
126 124 def get_node(self, repo, path):
127 125 n_path = path[len(repo.path) + 1:]
128 126 node = repo.get_changeset().get_node(n_path)
129 127 return node
130 128
131 129 def get_node_mtime(self, node):
132 130 return mktime(node.last_changeset.date.timetuple())
133 131
134 132 def add_doc(self, writer, path, repo, repo_name):
135 133 """Adding doc to writer this function itself fetches data from
136 134 the instance of vcs backend"""
137 135 node = self.get_node(repo, path)
138 136
139 137 #we just index the content of chosen files, and skip binary files
140 138 if node.extension in INDEX_EXTENSIONS and not node.is_binary:
141 139
142 140 u_content = node.content
143 141 if not isinstance(u_content, unicode):
144 142 log.warning(' >> %s Could not get this content as unicode '
145 143 'replacing with empty content', path)
146 144 u_content = u''
147 145 else:
148 146 log.debug(' >> %s [WITH CONTENT]' % path)
149 147
150 148 else:
151 149 log.debug(' >> %s' % path)
152 150 #just index file name without it's content
153 151 u_content = u''
154 152
155 153 writer.add_document(owner=unicode(repo.contact),
156 154 repository=safe_unicode(repo_name),
157 155 path=safe_unicode(path),
158 156 content=u_content,
159 157 modtime=self.get_node_mtime(node),
160 158 extension=node.extension)
161 159
162 160
163 161 def build_index(self):
164 162 if os.path.exists(self.index_location):
165 163 log.debug('removing previous index')
166 164 rmtree(self.index_location)
167 165
168 166 if not os.path.exists(self.index_location):
169 167 os.mkdir(self.index_location)
170 168
171 169 idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
172 170 writer = idx.writer()
173 171
174 172 for repo_name, repo in self.repo_paths.items():
175 173 log.debug('building index @ %s' % repo.path)
176 174
177 175 for idx_path in self.get_paths(repo):
178 176 self.add_doc(writer, idx_path, repo, repo_name)
179 177
180 178 log.debug('>> COMMITING CHANGES <<')
181 179 writer.commit(merge=True)
182 180 log.debug('>>> FINISHED BUILDING INDEX <<<')
183 181
184 182
185 183 def update_index(self):
186 184 log.debug('STARTING INCREMENTAL INDEXING UPDATE')
187 185
188 186 idx = open_dir(self.index_location, indexname=self.indexname)
189 187 # The set of all paths in the index
190 188 indexed_paths = set()
191 189 # The set of all paths we need to re-index
192 190 to_index = set()
193 191
194 192 reader = idx.reader()
195 193 writer = idx.writer()
196 194
197 195 # Loop over the stored fields in the index
198 196 for fields in reader.all_stored_fields():
199 197 indexed_path = fields['path']
200 198 indexed_paths.add(indexed_path)
201 199
202 200 repo = self.repo_paths[fields['repository']]
203 201
204 202 try:
205 203 node = self.get_node(repo, indexed_path)
206 204 except ChangesetError:
207 205 # This file was deleted since it was indexed
208 206 log.debug('removing from index %s' % indexed_path)
209 207 writer.delete_by_term('path', indexed_path)
210 208
211 209 else:
212 210 # Check if this file was changed since it was indexed
213 211 indexed_time = fields['modtime']
214 212 mtime = self.get_node_mtime(node)
215 213 if mtime > indexed_time:
216 214 # The file has changed, delete it and add it to the list of
217 215 # files to reindex
218 216 log.debug('adding to reindex list %s' % indexed_path)
219 217 writer.delete_by_term('path', indexed_path)
220 218 to_index.add(indexed_path)
221 219
222 220 # Loop over the files in the filesystem
223 221 # Assume we have a function that gathers the filenames of the
224 222 # documents to be indexed
225 223 for repo_name, repo in self.repo_paths.items():
226 224 for path in self.get_paths(repo):
227 225 if path in to_index or path not in indexed_paths:
228 226 # This is either a file that's changed, or a new file
229 227 # that wasn't indexed before. So index it!
230 228 self.add_doc(writer, path, repo, repo_name)
231 229 log.debug('re indexing %s' % path)
232 230
233 231 log.debug('>> COMMITING CHANGES <<')
234 232 writer.commit(merge=True)
235 233 log.debug('>>> FINISHED REBUILDING INDEX <<<')
236 234
237 235 def run(self, full_index=False):
238 236 """Run daemon"""
239 237 if full_index or self.initial:
240 238 self.build_index()
241 239 else:
242 240 self.update_index()
@@ -1,54 +1,52 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.https_fixup
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 middleware to handle https correctly
7 7
8 8 :created_on: May 23, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 from rhodecode.lib import str2bool
29 27
30 28 class HttpsFixup(object):
31 29 def __init__(self, app, config):
32 30 self.application = app
33 31 self.config = config
34 32
35 33 def __call__(self, environ, start_response):
36 34 self.__fixup(environ)
37 35 return self.application(environ, start_response)
38 36
39 37
40 38 def __fixup(self, environ):
41 39 """Function to fixup the environ as needed. In order to use this
42 40 middleware you should set this header inside your
43 41 proxy ie. nginx, apache etc.
44 42 """
45 43 proto = environ.get('HTTP_X_URL_SCHEME')
46 44
47 45 if str2bool(self.config.get('force_https')):
48 46 proto = 'https'
49 47
50 48 if proto == 'https':
51 49 environ['wsgi.url_scheme'] = proto
52 50 else:
53 51 environ['wsgi.url_scheme'] = 'http'
54 52 return None
@@ -1,275 +1,273 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplegit
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 7 It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 26
29 27 import os
30 28 import logging
31 29 import traceback
32 30
33 31 from dulwich import server as dulserver
34 32
35 33 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36 34
37 35 def handle(self):
38 36 write = lambda x: self.proto.write_sideband(1, x)
39 37
40 38 graph_walker = dulserver.ProtocolGraphWalker(self, self.repo.object_store,
41 39 self.repo.get_peeled)
42 40 objects_iter = self.repo.fetch_objects(
43 41 graph_walker.determine_wants, graph_walker, self.progress,
44 42 get_tagged=self.get_tagged)
45 43
46 44 # Do they want any objects?
47 45 if len(objects_iter) == 0:
48 46 return
49 47
50 48 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 49 dulserver.write_pack_data(dulserver.ProtocolFile(None, write), objects_iter,
52 50 len(objects_iter))
53 51 messages = []
54 52 messages.append('thank you for using rhodecode')
55 53
56 54 for msg in messages:
57 55 self.progress(msg + "\n")
58 56 # we are done
59 57 self.proto.write("0000")
60 58
61 59 dulserver.DEFAULT_HANDLERS = {
62 60 'git-upload-pack': SimpleGitUploadPackHandler,
63 61 'git-receive-pack': dulserver.ReceivePackHandler,
64 62 }
65 63
66 64 from dulwich.repo import Repo
67 65 from dulwich.web import HTTPGitApplication
68 66
69 67 from paste.auth.basic import AuthBasicAuthenticator
70 68 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71 69
72 70 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
73 71 from rhodecode.lib.utils import invalidate_cache, check_repo_fast
74 72 from rhodecode.model.user import UserModel
75 73
76 74 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
77 75
78 76 log = logging.getLogger(__name__)
79 77
80 78 def is_git(environ):
81 79 """Returns True if request's target is git server.
82 80 ``HTTP_USER_AGENT`` would then have git client version given.
83 81
84 82 :param environ:
85 83 """
86 84 http_user_agent = environ.get('HTTP_USER_AGENT')
87 85 if http_user_agent and http_user_agent.startswith('git'):
88 86 return True
89 87 return False
90 88
91 89 class SimpleGit(object):
92 90
93 91 def __init__(self, application, config):
94 92 self.application = application
95 93 self.config = config
96 94 #authenticate this git request using
97 95 self.authenticate = AuthBasicAuthenticator('', authfunc)
98 96 self.ipaddr = '0.0.0.0'
99 97 self.repository = None
100 98 self.username = None
101 99 self.action = None
102 100
103 101 def __call__(self, environ, start_response):
104 102 if not is_git(environ):
105 103 return self.application(environ, start_response)
106 104
107 105 proxy_key = 'HTTP_X_REAL_IP'
108 106 def_key = 'REMOTE_ADDR'
109 107 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
110 108 # skip passing error to error controller
111 109 environ['pylons.status_code_redirect'] = True
112 110
113 111 #======================================================================
114 112 # GET ACTION PULL or PUSH
115 113 #======================================================================
116 114 self.action = self.__get_action(environ)
117 115 try:
118 116 #==================================================================
119 117 # GET REPOSITORY NAME
120 118 #==================================================================
121 119 self.repo_name = self.__get_repository(environ)
122 120 except:
123 121 return HTTPInternalServerError()(environ, start_response)
124 122
125 123 #======================================================================
126 124 # CHECK ANONYMOUS PERMISSION
127 125 #======================================================================
128 126 if self.action in ['pull', 'push'] or self.action:
129 127 anonymous_user = self.__get_user('default')
130 128 self.username = anonymous_user.username
131 129 anonymous_perm = self.__check_permission(self.action, anonymous_user ,
132 130 self.repo_name)
133 131
134 132 if anonymous_perm is not True or anonymous_user.active is False:
135 133 if anonymous_perm is not True:
136 134 log.debug('Not enough credentials to access this repository'
137 135 'as anonymous user')
138 136 if anonymous_user.active is False:
139 137 log.debug('Anonymous access is disabled, running '
140 138 'authentication')
141 139 #==============================================================
142 140 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
143 141 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
144 142 #==============================================================
145 143
146 144 if not REMOTE_USER(environ):
147 145 self.authenticate.realm = str(self.config['rhodecode_realm'])
148 146 result = self.authenticate(environ)
149 147 if isinstance(result, str):
150 148 AUTH_TYPE.update(environ, 'basic')
151 149 REMOTE_USER.update(environ, result)
152 150 else:
153 151 return result.wsgi_application(environ, start_response)
154 152
155 153
156 154 #==============================================================
157 155 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
158 156 # BASIC AUTH
159 157 #==============================================================
160 158
161 159 if self.action in ['pull', 'push'] or self.action:
162 160 username = REMOTE_USER(environ)
163 161 try:
164 162 user = self.__get_user(username)
165 163 self.username = user.username
166 164 except:
167 165 log.error(traceback.format_exc())
168 166 return HTTPInternalServerError()(environ, start_response)
169 167
170 168 #check permissions for this repository
171 169 perm = self.__check_permission(self.action, user, self.repo_name)
172 170 if perm is not True:
173 171 print 'not allowed'
174 172 return HTTPForbidden()(environ, start_response)
175 173
176 174 self.extras = {'ip':self.ipaddr,
177 175 'username':self.username,
178 176 'action':self.action,
179 177 'repository':self.repo_name}
180 178
181 179 #===================================================================
182 180 # GIT REQUEST HANDLING
183 181 #===================================================================
184 182 self.basepath = self.config['base_path']
185 183 self.repo_path = os.path.join(self.basepath, self.repo_name)
186 184 #quick check if that dir exists...
187 185 if check_repo_fast(self.repo_name, self.basepath):
188 186 return HTTPNotFound()(environ, start_response)
189 187 try:
190 188 app = self.__make_app()
191 189 except:
192 190 log.error(traceback.format_exc())
193 191 return HTTPInternalServerError()(environ, start_response)
194 192
195 193 #invalidate cache on push
196 194 if self.action == 'push':
197 195 self.__invalidate_cache(self.repo_name)
198 196 messages = []
199 197 messages.append('thank you for using rhodecode')
200 198 return app(environ, start_response)
201 199 else:
202 200 return app(environ, start_response)
203 201
204 202
205 203 def __make_app(self):
206 204 backend = dulserver.DictBackend({'/' + self.repo_name: Repo(self.repo_path)})
207 205 gitserve = HTTPGitApplication(backend)
208 206
209 207 return gitserve
210 208
211 209 def __check_permission(self, action, user, repo_name):
212 210 """Checks permissions using action (push/pull) user and repository
213 211 name
214 212
215 213 :param action: push or pull action
216 214 :param user: user instance
217 215 :param repo_name: repository name
218 216 """
219 217 if action == 'push':
220 218 if not HasPermissionAnyMiddleware('repository.write',
221 219 'repository.admin')\
222 220 (user, repo_name):
223 221 return False
224 222
225 223 else:
226 224 #any other action need at least read permission
227 225 if not HasPermissionAnyMiddleware('repository.read',
228 226 'repository.write',
229 227 'repository.admin')\
230 228 (user, repo_name):
231 229 return False
232 230
233 231 return True
234 232
235 233
236 234 def __get_repository(self, environ):
237 235 """Get's repository name out of PATH_INFO header
238 236
239 237 :param environ: environ where PATH_INFO is stored
240 238 """
241 239 try:
242 240 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
243 241 if repo_name.endswith('/'):
244 242 repo_name = repo_name.rstrip('/')
245 243 except:
246 244 log.error(traceback.format_exc())
247 245 raise
248 246 repo_name = repo_name.split('/')[0]
249 247 return repo_name
250 248
251 249
252 250 def __get_user(self, username):
253 251 return UserModel().get_by_username(username, cache=True)
254 252
255 253 def __get_action(self, environ):
256 254 """Maps git request commands into a pull or push command.
257 255
258 256 :param environ:
259 257 """
260 258 service = environ['QUERY_STRING'].split('=')
261 259 if len(service) > 1:
262 260 service_cmd = service[1]
263 261 mapping = {'git-receive-pack': 'push',
264 262 'git-upload-pack': 'pull',
265 263 }
266 264
267 265 return mapping.get(service_cmd, service_cmd if service_cmd else 'other')
268 266 else:
269 267 return 'other'
270 268
271 269 def __invalidate_cache(self, repo_name):
272 270 """we know that some change was made to repositories and we should
273 271 invalidate the cache to see the changes right away but only for
274 272 push requests"""
275 273 invalidate_cache('get_repo_cached_%s' % repo_name)
@@ -1,272 +1,270 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplehg
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleHG middleware for handling mercurial protocol request
7 7 (push/clone etc.). It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 26
29 27 import os
30 28 import logging
31 29 import traceback
32 30
33 31 from mercurial.error import RepoError
34 32 from mercurial.hgweb import hgweb
35 33 from mercurial.hgweb.request import wsgiapplication
36 34
37 35 from paste.auth.basic import AuthBasicAuthenticator
38 36 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
39 37
40 38 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
41 39 from rhodecode.lib.utils import make_ui, invalidate_cache, \
42 40 check_repo_fast, ui_sections
43 41 from rhodecode.model.user import UserModel
44 42
45 43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
46 44
47 45 log = logging.getLogger(__name__)
48 46
49 47 def is_mercurial(environ):
50 48 """Returns True if request's target is mercurial server - header
51 49 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 50 """
53 51 http_accept = environ.get('HTTP_ACCEPT')
54 52 if http_accept and http_accept.startswith('application/mercurial'):
55 53 return True
56 54 return False
57 55
58 56 class SimpleHg(object):
59 57
60 58 def __init__(self, application, config):
61 59 self.application = application
62 60 self.config = config
63 61 #authenticate this mercurial request using authfunc
64 62 self.authenticate = AuthBasicAuthenticator('', authfunc)
65 63 self.ipaddr = '0.0.0.0'
66 64 self.repo_name = None
67 65 self.username = None
68 66 self.action = None
69 67
70 68 def __call__(self, environ, start_response):
71 69 if not is_mercurial(environ):
72 70 return self.application(environ, start_response)
73 71
74 72 proxy_key = 'HTTP_X_REAL_IP'
75 73 def_key = 'REMOTE_ADDR'
76 74 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
77 75 # skip passing error to error controller
78 76 environ['pylons.status_code_redirect'] = True
79 77
80 78 #======================================================================
81 79 # GET ACTION PULL or PUSH
82 80 #======================================================================
83 81 self.action = self.__get_action(environ)
84 82 try:
85 83 #==================================================================
86 84 # GET REPOSITORY NAME
87 85 #==================================================================
88 86 self.repo_name = self.__get_repository(environ)
89 87 except:
90 88 return HTTPInternalServerError()(environ, start_response)
91 89
92 90 #======================================================================
93 91 # CHECK ANONYMOUS PERMISSION
94 92 #======================================================================
95 93 if self.action in ['pull', 'push']:
96 94 anonymous_user = self.__get_user('default')
97 95 self.username = anonymous_user.username
98 96 anonymous_perm = self.__check_permission(self.action, anonymous_user ,
99 97 self.repo_name)
100 98
101 99 if anonymous_perm is not True or anonymous_user.active is False:
102 100 if anonymous_perm is not True:
103 101 log.debug('Not enough credentials to access this repository'
104 102 'as anonymous user')
105 103 if anonymous_user.active is False:
106 104 log.debug('Anonymous access is disabled, running '
107 105 'authentication')
108 106 #==============================================================
109 107 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
110 108 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
111 109 #==============================================================
112 110
113 111 if not REMOTE_USER(environ):
114 112 self.authenticate.realm = str(self.config['rhodecode_realm'])
115 113 result = self.authenticate(environ)
116 114 if isinstance(result, str):
117 115 AUTH_TYPE.update(environ, 'basic')
118 116 REMOTE_USER.update(environ, result)
119 117 else:
120 118 return result.wsgi_application(environ, start_response)
121 119
122 120
123 121 #==============================================================
124 122 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
125 123 # BASIC AUTH
126 124 #==============================================================
127 125
128 126 if self.action in ['pull', 'push']:
129 127 username = REMOTE_USER(environ)
130 128 try:
131 129 user = self.__get_user(username)
132 130 self.username = user.username
133 131 except:
134 132 log.error(traceback.format_exc())
135 133 return HTTPInternalServerError()(environ, start_response)
136 134
137 135 #check permissions for this repository
138 136 perm = self.__check_permission(self.action, user, self.repo_name)
139 137 if perm is not True:
140 138 return HTTPForbidden()(environ, start_response)
141 139
142 140 self.extras = {'ip':self.ipaddr,
143 141 'username':self.username,
144 142 'action':self.action,
145 143 'repository':self.repo_name}
146 144
147 145 #===================================================================
148 146 # MERCURIAL REQUEST HANDLING
149 147 #===================================================================
150 148 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
151 149 self.baseui = make_ui('db')
152 150 self.basepath = self.config['base_path']
153 151 self.repo_path = os.path.join(self.basepath, self.repo_name)
154 152
155 153 #quick check if that dir exists...
156 154 if check_repo_fast(self.repo_name, self.basepath):
157 155 return HTTPNotFound()(environ, start_response)
158 156 try:
159 157 app = wsgiapplication(self.__make_app)
160 158 except RepoError, e:
161 159 if str(e).find('not found') != -1:
162 160 return HTTPNotFound()(environ, start_response)
163 161 except Exception:
164 162 log.error(traceback.format_exc())
165 163 return HTTPInternalServerError()(environ, start_response)
166 164
167 165 #invalidate cache on push
168 166 if self.action == 'push':
169 167 self.__invalidate_cache(self.repo_name)
170 168
171 169 return app(environ, start_response)
172 170
173 171
174 172 def __make_app(self):
175 173 """Make an wsgi application using hgweb, and my generated baseui
176 174 instance
177 175 """
178 176
179 177 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
180 178 return self.__load_web_settings(hgserve, self.extras)
181 179
182 180
183 181 def __check_permission(self, action, user, repo_name):
184 182 """Checks permissions using action (push/pull) user and repository
185 183 name
186 184
187 185 :param action: push or pull action
188 186 :param user: user instance
189 187 :param repo_name: repository name
190 188 """
191 189 if action == 'push':
192 190 if not HasPermissionAnyMiddleware('repository.write',
193 191 'repository.admin')\
194 192 (user, repo_name):
195 193 return False
196 194
197 195 else:
198 196 #any other action need at least read permission
199 197 if not HasPermissionAnyMiddleware('repository.read',
200 198 'repository.write',
201 199 'repository.admin')\
202 200 (user, repo_name):
203 201 return False
204 202
205 203 return True
206 204
207 205
208 206 def __get_repository(self, environ):
209 207 """Get's repository name out of PATH_INFO header
210 208
211 209 :param environ: environ where PATH_INFO is stored
212 210 """
213 211 try:
214 212 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
215 213 if repo_name.endswith('/'):
216 214 repo_name = repo_name.rstrip('/')
217 215 except:
218 216 log.error(traceback.format_exc())
219 217 raise
220 218
221 219 return repo_name
222 220
223 221 def __get_user(self, username):
224 222 return UserModel().get_by_username(username, cache=True)
225 223
226 224 def __get_action(self, environ):
227 225 """Maps mercurial request commands into a clone,pull or push command.
228 226 This should always return a valid command string
229 227
230 228 :param environ:
231 229 """
232 230 mapping = {'changegroup': 'pull',
233 231 'changegroupsubset': 'pull',
234 232 'stream_out': 'pull',
235 233 'listkeys': 'pull',
236 234 'unbundle': 'push',
237 235 'pushkey': 'push', }
238 236 for qry in environ['QUERY_STRING'].split('&'):
239 237 if qry.startswith('cmd'):
240 238 cmd = qry.split('=')[-1]
241 239 if mapping.has_key(cmd):
242 240 return mapping[cmd]
243 241 else:
244 242 return 'pull'
245 243
246 244 def __invalidate_cache(self, repo_name):
247 245 """we know that some change was made to repositories and we should
248 246 invalidate the cache to see the changes right away but only for
249 247 push requests"""
250 248 invalidate_cache('get_repo_cached_%s' % repo_name)
251 249
252 250
253 251 def __load_web_settings(self, hgserve, extras={}):
254 252 #set the global ui for hgserve instance passed
255 253 hgserve.repo.ui = self.baseui
256 254
257 255 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
258 256
259 257 #inject some additional parameters that will be available in ui
260 258 #for hooks
261 259 for k, v in extras.items():
262 260 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
263 261
264 262 repoui = make_ui('file', hgrc, False)
265 263
266 264 if repoui:
267 265 #overwrite our ui instance with the section from hgrc file
268 266 for section in ui_sections:
269 267 for k, v in repoui.configitems(section):
270 268 hgserve.repo.ui.setconfig(section, k, v)
271 269
272 270 return hgserve
@@ -1,688 +1,686 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import os
29 27 import logging
30 28 import datetime
31 29 import traceback
32 30 import paste
33 31 import beaker
34 32
35 33 from paste.script.command import Command, BadCommand
36 34
37 35 from UserDict import DictMixin
38 36
39 37 from mercurial import ui, config, hg
40 38 from mercurial.error import RepoError
41 39
42 40 from webhelpers.text import collapse, remove_formatting, strip_tags
43 41
44 42 from vcs.backends.base import BaseChangeset
45 43 from vcs.utils.lazy import LazyProperty
46 44
47 45 from rhodecode.model import meta
48 46 from rhodecode.model.caching_query import FromCache
49 47 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group
50 48 from rhodecode.model.repo import RepoModel
51 49 from rhodecode.model.user import UserModel
52 50
53 51 log = logging.getLogger(__name__)
54 52
55 53
56 54 def recursive_replace(str, replace=' '):
57 55 """Recursive replace of given sign to just one instance
58 56
59 57 :param str: given string
60 58 :param replace: char to find and replace multiple instances
61 59
62 60 Examples::
63 61 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
64 62 'Mighty-Mighty-Bo-sstones'
65 63 """
66 64
67 65 if str.find(replace * 2) == -1:
68 66 return str
69 67 else:
70 68 str = str.replace(replace * 2, replace)
71 69 return recursive_replace(str, replace)
72 70
73 71 def repo_name_slug(value):
74 72 """Return slug of name of repository
75 73 This function is called on each creation/modification
76 74 of repository to prevent bad names in repo
77 75 """
78 76
79 77 slug = remove_formatting(value)
80 78 slug = strip_tags(slug)
81 79
82 80 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
83 81 slug = slug.replace(c, '-')
84 82 slug = recursive_replace(slug, '-')
85 83 slug = collapse(slug, '-')
86 84 return slug
87 85
88 86 def get_repo_slug(request):
89 87 return request.environ['pylons.routes_dict'].get('repo_name')
90 88
91 89 def action_logger(user, action, repo, ipaddr='', sa=None):
92 90 """
93 91 Action logger for various actions made by users
94 92
95 93 :param user: user that made this action, can be a unique username string or
96 94 object containing user_id attribute
97 95 :param action: action to log, should be on of predefined unique actions for
98 96 easy translations
99 97 :param repo: string name of repository or object containing repo_id,
100 98 that action was made on
101 99 :param ipaddr: optional ip address from what the action was made
102 100 :param sa: optional sqlalchemy session
103 101
104 102 """
105 103
106 104 if not sa:
107 105 sa = meta.Session()
108 106
109 107 try:
110 108 um = UserModel()
111 109 if hasattr(user, 'user_id'):
112 110 user_obj = user
113 111 elif isinstance(user, basestring):
114 112 user_obj = um.get_by_username(user, cache=False)
115 113 else:
116 114 raise Exception('You have to provide user object or username')
117 115
118 116
119 117 rm = RepoModel()
120 118 if hasattr(repo, 'repo_id'):
121 119 repo_obj = rm.get(repo.repo_id, cache=False)
122 120 repo_name = repo_obj.repo_name
123 121 elif isinstance(repo, basestring):
124 122 repo_name = repo.lstrip('/')
125 123 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
126 124 else:
127 125 raise Exception('You have to provide repository to action logger')
128 126
129 127
130 128 user_log = UserLog()
131 129 user_log.user_id = user_obj.user_id
132 130 user_log.action = action
133 131
134 132 user_log.repository_id = repo_obj.repo_id
135 133 user_log.repository_name = repo_name
136 134
137 135 user_log.action_date = datetime.datetime.now()
138 136 user_log.user_ip = ipaddr
139 137 sa.add(user_log)
140 138 sa.commit()
141 139
142 140 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
143 141 except:
144 142 log.error(traceback.format_exc())
145 143 sa.rollback()
146 144
147 145 def get_repos(path, recursive=False):
148 146 """
149 147 Scans given path for repos and return (name,(type,path)) tuple
150 148
151 149 :param path: path to scann for repositories
152 150 :param recursive: recursive search and return names with subdirs in front
153 151 """
154 152 from vcs.utils.helpers import get_scm
155 153 from vcs.exceptions import VCSError
156 154
157 155 if path.endswith('/'):
158 156 #add ending slash for better results
159 157 path = path[:-1]
160 158
161 159 def _get_repos(p):
162 160 for dirpath in os.listdir(p):
163 161 if os.path.isfile(os.path.join(p, dirpath)):
164 162 continue
165 163 cur_path = os.path.join(p, dirpath)
166 164 try:
167 165 scm_info = get_scm(cur_path)
168 166 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
169 167 except VCSError:
170 168 if not recursive:
171 169 continue
172 170 #check if this dir containts other repos for recursive scan
173 171 rec_path = os.path.join(p, dirpath)
174 172 if os.path.isdir(rec_path):
175 173 for inner_scm in _get_repos(rec_path):
176 174 yield inner_scm
177 175
178 176 return _get_repos(path)
179 177
180 178 def check_repo_fast(repo_name, base_path):
181 179 """
182 180 Check given path for existence of directory
183 181 :param repo_name:
184 182 :param base_path:
185 183
186 184 :return False: if this directory is present
187 185 """
188 186 if os.path.isdir(os.path.join(base_path, repo_name)):return False
189 187 return True
190 188
191 189 def check_repo(repo_name, base_path, verify=True):
192 190
193 191 repo_path = os.path.join(base_path, repo_name)
194 192
195 193 try:
196 194 if not check_repo_fast(repo_name, base_path):
197 195 return False
198 196 r = hg.repository(ui.ui(), repo_path)
199 197 if verify:
200 198 hg.verify(r)
201 199 #here we hnow that repo exists it was verified
202 200 log.info('%s repo is already created', repo_name)
203 201 return False
204 202 except RepoError:
205 203 #it means that there is no valid repo there...
206 204 log.info('%s repo is free for creation', repo_name)
207 205 return True
208 206
209 207 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
210 208 while True:
211 209 ok = raw_input(prompt)
212 210 if ok in ('y', 'ye', 'yes'): return True
213 211 if ok in ('n', 'no', 'nop', 'nope'): return False
214 212 retries = retries - 1
215 213 if retries < 0: raise IOError
216 214 print complaint
217 215
218 216 #propagated from mercurial documentation
219 217 ui_sections = ['alias', 'auth',
220 218 'decode/encode', 'defaults',
221 219 'diff', 'email',
222 220 'extensions', 'format',
223 221 'merge-patterns', 'merge-tools',
224 222 'hooks', 'http_proxy',
225 223 'smtp', 'patch',
226 224 'paths', 'profiling',
227 225 'server', 'trusted',
228 226 'ui', 'web', ]
229 227
230 228 def make_ui(read_from='file', path=None, checkpaths=True):
231 229 """A function that will read python rc files or database
232 230 and make an mercurial ui object from read options
233 231
234 232 :param path: path to mercurial config file
235 233 :param checkpaths: check the path
236 234 :param read_from: read from 'file' or 'db'
237 235 """
238 236
239 237 baseui = ui.ui()
240 238
241 239 #clean the baseui object
242 240 baseui._ocfg = config.config()
243 241 baseui._ucfg = config.config()
244 242 baseui._tcfg = config.config()
245 243
246 244 if read_from == 'file':
247 245 if not os.path.isfile(path):
248 246 log.warning('Unable to read config file %s' % path)
249 247 return False
250 248 log.debug('reading hgrc from %s', path)
251 249 cfg = config.config()
252 250 cfg.read(path)
253 251 for section in ui_sections:
254 252 for k, v in cfg.items(section):
255 253 log.debug('settings ui from file[%s]%s:%s', section, k, v)
256 254 baseui.setconfig(section, k, v)
257 255
258 256
259 257 elif read_from == 'db':
260 258 sa = meta.Session()
261 259 ret = sa.query(RhodeCodeUi)\
262 260 .options(FromCache("sql_cache_short",
263 261 "get_hg_ui_settings")).all()
264 262
265 263 hg_ui = ret
266 264 for ui_ in hg_ui:
267 265 if ui_.ui_active:
268 266 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
269 267 ui_.ui_key, ui_.ui_value)
270 268 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
271 269
272 270 meta.Session.remove()
273 271 return baseui
274 272
275 273
276 274 def set_rhodecode_config(config):
277 275 """Updates pylons config with new settings from database
278 276
279 277 :param config:
280 278 """
281 279 from rhodecode.model.settings import SettingsModel
282 280 hgsettings = SettingsModel().get_app_settings()
283 281
284 282 for k, v in hgsettings.items():
285 283 config[k] = v
286 284
287 285 def invalidate_cache(cache_key, *args):
288 286 """Puts cache invalidation task into db for
289 287 further global cache invalidation
290 288 """
291 289
292 290 from rhodecode.model.scm import ScmModel
293 291
294 292 if cache_key.startswith('get_repo_cached_'):
295 293 name = cache_key.split('get_repo_cached_')[-1]
296 294 ScmModel().mark_for_invalidation(name)
297 295
298 296 class EmptyChangeset(BaseChangeset):
299 297 """
300 298 An dummy empty changeset. It's possible to pass hash when creating
301 299 an EmptyChangeset
302 300 """
303 301
304 302 def __init__(self, cs='0' * 40):
305 303 self._empty_cs = cs
306 304 self.revision = -1
307 305 self.message = ''
308 306 self.author = ''
309 307 self.date = ''
310 308
311 309 @LazyProperty
312 310 def raw_id(self):
313 311 """Returns raw string identifying this changeset, useful for web
314 312 representation.
315 313 """
316 314
317 315 return self._empty_cs
318 316
319 317 @LazyProperty
320 318 def short_id(self):
321 319 return self.raw_id[:12]
322 320
323 321 def get_file_changeset(self, path):
324 322 return self
325 323
326 324 def get_file_content(self, path):
327 325 return u''
328 326
329 327 def get_file_size(self, path):
330 328 return 0
331 329
332 330 def map_groups(groups):
333 331 """Checks for groups existence, and creates groups structures.
334 332 It returns last group in structure
335 333
336 334 :param groups: list of groups structure
337 335 """
338 336 sa = meta.Session()
339 337
340 338 parent = None
341 339 group = None
342 340 for lvl, group_name in enumerate(groups[:-1]):
343 341 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
344 342
345 343 if group is None:
346 344 group = Group(group_name, parent)
347 345 sa.add(group)
348 346 sa.commit()
349 347
350 348 parent = group
351 349
352 350 return group
353 351
354 352 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
355 353 """maps all repos given in initial_repo_list, non existing repositories
356 354 are created, if remove_obsolete is True it also check for db entries
357 355 that are not in initial_repo_list and removes them.
358 356
359 357 :param initial_repo_list: list of repositories found by scanning methods
360 358 :param remove_obsolete: check for obsolete entries in database
361 359 """
362 360
363 361 sa = meta.Session()
364 362 rm = RepoModel()
365 363 user = sa.query(User).filter(User.admin == True).first()
366 364 added = []
367 365 for name, repo in initial_repo_list.items():
368 366 group = map_groups(name.split('/'))
369 367 if not rm.get_by_repo_name(name, cache=False):
370 368 log.info('repository %s not found creating default', name)
371 369 added.append(name)
372 370 form_data = {
373 371 'repo_name':name,
374 372 'repo_type':repo.alias,
375 373 'description':repo.description \
376 374 if repo.description != 'unknown' else \
377 375 '%s repository' % name,
378 376 'private':False,
379 377 'group_id':getattr(group, 'group_id', None)
380 378 }
381 379 rm.create(form_data, user, just_db=True)
382 380
383 381 removed = []
384 382 if remove_obsolete:
385 383 #remove from database those repositories that are not in the filesystem
386 384 for repo in sa.query(Repository).all():
387 385 if repo.repo_name not in initial_repo_list.keys():
388 386 removed.append(repo.repo_name)
389 387 sa.delete(repo)
390 388 sa.commit()
391 389
392 390 return added, removed
393 391 class OrderedDict(dict, DictMixin):
394 392
395 393 def __init__(self, *args, **kwds):
396 394 if len(args) > 1:
397 395 raise TypeError('expected at most 1 arguments, got %d' % len(args))
398 396 try:
399 397 self.__end
400 398 except AttributeError:
401 399 self.clear()
402 400 self.update(*args, **kwds)
403 401
404 402 def clear(self):
405 403 self.__end = end = []
406 404 end += [None, end, end] # sentinel node for doubly linked list
407 405 self.__map = {} # key --> [key, prev, next]
408 406 dict.clear(self)
409 407
410 408 def __setitem__(self, key, value):
411 409 if key not in self:
412 410 end = self.__end
413 411 curr = end[1]
414 412 curr[2] = end[1] = self.__map[key] = [key, curr, end]
415 413 dict.__setitem__(self, key, value)
416 414
417 415 def __delitem__(self, key):
418 416 dict.__delitem__(self, key)
419 417 key, prev, next = self.__map.pop(key)
420 418 prev[2] = next
421 419 next[1] = prev
422 420
423 421 def __iter__(self):
424 422 end = self.__end
425 423 curr = end[2]
426 424 while curr is not end:
427 425 yield curr[0]
428 426 curr = curr[2]
429 427
430 428 def __reversed__(self):
431 429 end = self.__end
432 430 curr = end[1]
433 431 while curr is not end:
434 432 yield curr[0]
435 433 curr = curr[1]
436 434
437 435 def popitem(self, last=True):
438 436 if not self:
439 437 raise KeyError('dictionary is empty')
440 438 if last:
441 439 key = reversed(self).next()
442 440 else:
443 441 key = iter(self).next()
444 442 value = self.pop(key)
445 443 return key, value
446 444
447 445 def __reduce__(self):
448 446 items = [[k, self[k]] for k in self]
449 447 tmp = self.__map, self.__end
450 448 del self.__map, self.__end
451 449 inst_dict = vars(self).copy()
452 450 self.__map, self.__end = tmp
453 451 if inst_dict:
454 452 return (self.__class__, (items,), inst_dict)
455 453 return self.__class__, (items,)
456 454
457 455 def keys(self):
458 456 return list(self)
459 457
460 458 setdefault = DictMixin.setdefault
461 459 update = DictMixin.update
462 460 pop = DictMixin.pop
463 461 values = DictMixin.values
464 462 items = DictMixin.items
465 463 iterkeys = DictMixin.iterkeys
466 464 itervalues = DictMixin.itervalues
467 465 iteritems = DictMixin.iteritems
468 466
469 467 def __repr__(self):
470 468 if not self:
471 469 return '%s()' % (self.__class__.__name__,)
472 470 return '%s(%r)' % (self.__class__.__name__, self.items())
473 471
474 472 def copy(self):
475 473 return self.__class__(self)
476 474
477 475 @classmethod
478 476 def fromkeys(cls, iterable, value=None):
479 477 d = cls()
480 478 for key in iterable:
481 479 d[key] = value
482 480 return d
483 481
484 482 def __eq__(self, other):
485 483 if isinstance(other, OrderedDict):
486 484 return len(self) == len(other) and self.items() == other.items()
487 485 return dict.__eq__(self, other)
488 486
489 487 def __ne__(self, other):
490 488 return not self == other
491 489
492 490
493 491 #set cache regions for beaker so celery can utilise it
494 492 def add_cache(settings):
495 493 cache_settings = {'regions':None}
496 494 for key in settings.keys():
497 495 for prefix in ['beaker.cache.', 'cache.']:
498 496 if key.startswith(prefix):
499 497 name = key.split(prefix)[1].strip()
500 498 cache_settings[name] = settings[key].strip()
501 499 if cache_settings['regions']:
502 500 for region in cache_settings['regions'].split(','):
503 501 region = region.strip()
504 502 region_settings = {}
505 503 for key, value in cache_settings.items():
506 504 if key.startswith(region):
507 505 region_settings[key.split('.')[1]] = value
508 506 region_settings['expire'] = int(region_settings.get('expire',
509 507 60))
510 508 region_settings.setdefault('lock_dir',
511 509 cache_settings.get('lock_dir'))
512 510 region_settings.setdefault('data_dir',
513 511 cache_settings.get('data_dir'))
514 512
515 513 if 'type' not in region_settings:
516 514 region_settings['type'] = cache_settings.get('type',
517 515 'memory')
518 516 beaker.cache.cache_regions[region] = region_settings
519 517
520 518 def get_current_revision():
521 519 """Returns tuple of (number, id) from repository containing this package
522 520 or None if repository could not be found.
523 521 """
524 522
525 523 try:
526 524 from vcs import get_repo
527 525 from vcs.utils.helpers import get_scm
528 526 from vcs.exceptions import RepositoryError, VCSError
529 527 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
530 528 scm = get_scm(repopath)[0]
531 529 repo = get_repo(path=repopath, alias=scm)
532 530 tip = repo.get_changeset()
533 531 return (tip.revision, tip.short_id)
534 532 except (ImportError, RepositoryError, VCSError), err:
535 533 logging.debug("Cannot retrieve rhodecode's revision. Original error "
536 534 "was: %s" % err)
537 535 return None
538 536
539 537 #===============================================================================
540 538 # TEST FUNCTIONS AND CREATORS
541 539 #===============================================================================
542 540 def create_test_index(repo_location, full_index):
543 541 """Makes default test index
544 542 :param repo_location:
545 543 :param full_index:
546 544 """
547 545 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
548 546 from rhodecode.lib.pidlock import DaemonLock, LockHeld
549 547 import shutil
550 548
551 549 index_location = os.path.join(repo_location, 'index')
552 550 if os.path.exists(index_location):
553 551 shutil.rmtree(index_location)
554 552
555 553 try:
556 554 l = DaemonLock()
557 555 WhooshIndexingDaemon(index_location=index_location,
558 556 repo_location=repo_location)\
559 557 .run(full_index=full_index)
560 558 l.release()
561 559 except LockHeld:
562 560 pass
563 561
564 562 def create_test_env(repos_test_path, config):
565 563 """Makes a fresh database and
566 564 install test repository into tmp dir
567 565 """
568 566 from rhodecode.lib.db_manage import DbManage
569 567 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
570 568 HG_FORK, GIT_FORK, TESTS_TMP_PATH
571 569 import tarfile
572 570 import shutil
573 571 from os.path import dirname as dn, join as jn, abspath
574 572
575 573 log = logging.getLogger('TestEnvCreator')
576 574 # create logger
577 575 log.setLevel(logging.DEBUG)
578 576 log.propagate = True
579 577 # create console handler and set level to debug
580 578 ch = logging.StreamHandler()
581 579 ch.setLevel(logging.DEBUG)
582 580
583 581 # create formatter
584 582 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
585 583
586 584 # add formatter to ch
587 585 ch.setFormatter(formatter)
588 586
589 587 # add ch to logger
590 588 log.addHandler(ch)
591 589
592 590 #PART ONE create db
593 591 dbconf = config['sqlalchemy.db1.url']
594 592 log.debug('making test db %s', dbconf)
595 593
596 594 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
597 595 tests=True)
598 596 dbmanage.create_tables(override=True)
599 597 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
600 598 dbmanage.create_default_user()
601 599 dbmanage.admin_prompt()
602 600 dbmanage.create_permissions()
603 601 dbmanage.populate_default_permissions()
604 602
605 603 #PART TWO make test repo
606 604 log.debug('making test vcs repositories')
607 605
608 606 #remove old one from previos tests
609 607 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
610 608
611 609 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
612 610 log.debug('removing %s', r)
613 611 shutil.rmtree(jn(TESTS_TMP_PATH, r))
614 612
615 613 #CREATE DEFAULT HG REPOSITORY
616 614 cur_dir = dn(dn(abspath(__file__)))
617 615 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
618 616 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
619 617 tar.close()
620 618
621 619
622 620 #==============================================================================
623 621 # PASTER COMMANDS
624 622 #==============================================================================
625 623
626 624 class BasePasterCommand(Command):
627 625 """
628 626 Abstract Base Class for paster commands.
629 627
630 628 The celery commands are somewhat aggressive about loading
631 629 celery.conf, and since our module sets the `CELERY_LOADER`
632 630 environment variable to our loader, we have to bootstrap a bit and
633 631 make sure we've had a chance to load the pylons config off of the
634 632 command line, otherwise everything fails.
635 633 """
636 634 min_args = 1
637 635 min_args_error = "Please provide a paster config file as an argument."
638 636 takes_config_file = 1
639 637 requires_config_file = True
640 638
641 639 def notify_msg(self, msg, log=False):
642 640 """Make a notification to user, additionally if logger is passed
643 641 it logs this action using given logger
644 642
645 643 :param msg: message that will be printed to user
646 644 :param log: logging instance, to use to additionally log this message
647 645
648 646 """
649 647 if log and isinstance(log, logging):
650 648 log(msg)
651 649
652 650
653 651 def run(self, args):
654 652 """
655 653 Overrides Command.run
656 654
657 655 Checks for a config file argument and loads it.
658 656 """
659 657 if len(args) < self.min_args:
660 658 raise BadCommand(
661 659 self.min_args_error % {'min_args': self.min_args,
662 660 'actual_args': len(args)})
663 661
664 662 # Decrement because we're going to lob off the first argument.
665 663 # @@ This is hacky
666 664 self.min_args -= 1
667 665 self.bootstrap_config(args[0])
668 666 self.update_parser()
669 667 return super(BasePasterCommand, self).run(args[1:])
670 668
671 669 def update_parser(self):
672 670 """
673 671 Abstract method. Allows for the class's parser to be updated
674 672 before the superclass's `run` method is called. Necessary to
675 673 allow options/arguments to be passed through to the underlying
676 674 celery command.
677 675 """
678 676 raise NotImplementedError("Abstract Method.")
679 677
680 678 def bootstrap_config(self, conf):
681 679 """
682 680 Loads the pylons configuration.
683 681 """
684 682 from pylons import config as pylonsconfig
685 683
686 684 path_to_ini_file = os.path.realpath(conf)
687 685 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
688 686 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,73 +1,71 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 The application's model objects
7 7
8 8 :created_on: Nov 25, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12
13 13
14 14 :example:
15 15
16 16 .. code-block:: python
17 17
18 18 from paste.deploy import appconfig
19 19 from pylons import config
20 20 from sqlalchemy import engine_from_config
21 21 from rhodecode.config.environment import load_environment
22 22
23 23 conf = appconfig('config:development.ini', relative_to = './../../')
24 24 load_environment(conf.global_conf, conf.local_conf)
25 25
26 26 engine = engine_from_config(config, 'sqlalchemy.')
27 27 init_model(engine)
28 28 # RUN YOUR CODE HERE
29 29
30 30 """
31 # This program is free software; you can redistribute it and/or
32 # modify it under the terms of the GNU General Public License
33 # as published by the Free Software Foundation; version 2
34 # of the License or (at your opinion) any later version of the license.
31 # This program is free software: you can redistribute it and/or modify
32 # it under the terms of the GNU General Public License as published by
33 # the Free Software Foundation, either version 3 of the License, or
34 # (at your option) any later version.
35 35 #
36 36 # This program is distributed in the hope that it will be useful,
37 37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
38 38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 39 # GNU General Public License for more details.
40 40 #
41 41 # You should have received a copy of the GNU General Public License
42 # along with this program; if not, write to the Free Software
43 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
44 # MA 02110-1301, USA.
42 # along with this program. If not, see <http://www.gnu.org/licenses/>.
45 43
46 44 import logging
47 45
48 46 from rhodecode.model import meta
49 47
50 48 log = logging.getLogger(__name__)
51 49
52 50 def init_model(engine):
53 51 """Initializes db session, bind the engine with the metadata,
54 52 Call this before using any of the tables or classes in the model, preferably
55 53 once in application start
56 54
57 55 :param engine: engine to bind to
58 56 """
59 57 log.info("initializing db for %s", engine)
60 58 meta.Base.metadata.bind = engine
61 59
62 60 class BaseModel(object):
63 61 """Base Model for all RhodeCode models, it adds sql alchemy session
64 62 into instance of model
65 63
66 64 :param sa: If passed it reuses this session instead of creating a new one
67 65 """
68 66
69 67 def __init__(self, sa=None):
70 68 if sa is not None:
71 69 self.sa = sa
72 70 else:
73 71 self.sa = meta.Session()
@@ -1,389 +1,387 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import os
29 27 import logging
30 28 import datetime
31 29 from datetime import date
32 30
33 31 from sqlalchemy import *
34 32 from sqlalchemy.exc import DatabaseError
35 33 from sqlalchemy.orm import relationship, backref
36 34 from sqlalchemy.orm.interfaces import MapperExtension
37 35
38 36 from rhodecode.model.meta import Base, Session
39 37
40 38 log = logging.getLogger(__name__)
41 39
42 40 #==============================================================================
43 41 # MAPPER EXTENSIONS
44 42 #==============================================================================
45 43
46 44 class RepositoryMapper(MapperExtension):
47 45 def after_update(self, mapper, connection, instance):
48 46 pass
49 47
50 48
51 49 class RhodeCodeSettings(Base):
52 50 __tablename__ = 'rhodecode_settings'
53 51 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
54 52 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
55 53 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
56 54 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
57 55
58 56 def __init__(self, k='', v=''):
59 57 self.app_settings_name = k
60 58 self.app_settings_value = v
61 59
62 60 def __repr__(self):
63 61 return "<%s('%s:%s')>" % (self.__class__.__name__,
64 62 self.app_settings_name, self.app_settings_value)
65 63
66 64 class RhodeCodeUi(Base):
67 65 __tablename__ = 'rhodecode_ui'
68 66 __table_args__ = {'useexisting':True}
69 67 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
70 68 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
71 69 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
72 70 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
73 71 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
74 72
75 73
76 74 class User(Base):
77 75 __tablename__ = 'users'
78 76 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
79 77 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
80 78 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
81 79 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
82 80 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
83 81 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
84 82 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
85 83 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
86 84 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
87 85 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
88 86 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
89 87 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
90 88
91 89 user_log = relationship('UserLog', cascade='all')
92 90 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
93 91
94 92 repositories = relationship('Repository')
95 93 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
96 94
97 95 group_member = relationship('UsersGroupMember', cascade='all')
98 96
99 97 @property
100 98 def full_contact(self):
101 99 return '%s %s <%s>' % (self.name, self.lastname, self.email)
102 100
103 101 @property
104 102 def short_contact(self):
105 103 return '%s %s' % (self.name, self.lastname)
106 104
107 105
108 106 @property
109 107 def is_admin(self):
110 108 return self.admin
111 109
112 110 def __repr__(self):
113 111 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
114 112 self.user_id, self.username)
115 113
116 114 @classmethod
117 115 def by_username(cls, username):
118 116 return Session.query(cls).filter(cls.username == username).one()
119 117
120 118
121 119 def update_lastlogin(self):
122 120 """Update user lastlogin"""
123 121
124 122 try:
125 123 session = Session.object_session(self)
126 124 self.last_login = datetime.datetime.now()
127 125 session.add(self)
128 126 session.commit()
129 127 log.debug('updated user %s lastlogin', self.username)
130 128 except (DatabaseError,):
131 129 session.rollback()
132 130
133 131
134 132 class UserLog(Base):
135 133 __tablename__ = 'user_logs'
136 134 __table_args__ = {'useexisting':True}
137 135 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
138 136 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
139 137 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
140 138 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
141 139 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
142 140 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 141 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
144 142
145 143 @property
146 144 def action_as_day(self):
147 145 return date(*self.action_date.timetuple()[:3])
148 146
149 147 user = relationship('User')
150 148 repository = relationship('Repository')
151 149
152 150
153 151 class UsersGroup(Base):
154 152 __tablename__ = 'users_groups'
155 153 __table_args__ = {'useexisting':True}
156 154
157 155 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
158 156 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
159 157 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
160 158
161 159 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
162 160
163 161 class UsersGroupMember(Base):
164 162 __tablename__ = 'users_groups_members'
165 163 __table_args__ = {'useexisting':True}
166 164
167 165 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
168 166 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
169 167 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
170 168
171 169 user = relationship('User', lazy='joined')
172 170 users_group = relationship('UsersGroup')
173 171
174 172 def __init__(self, gr_id='', u_id=''):
175 173 self.users_group_id = gr_id
176 174 self.user_id = u_id
177 175
178 176 class Repository(Base):
179 177 __tablename__ = 'repositories'
180 178 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
181 179 __mapper_args__ = {'extension':RepositoryMapper()}
182 180
183 181 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
184 182 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
185 183 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
186 184 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
187 185 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
188 186 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
189 187 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
190 188 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
191 189 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
192 190 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
193 191 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
194 192
195 193
196 194 user = relationship('User')
197 195 fork = relationship('Repository', remote_side=repo_id)
198 196 group = relationship('Group')
199 197 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
200 198 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
201 199 stats = relationship('Statistics', cascade='all', uselist=False)
202 200
203 201 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
204 202
205 203 logs = relationship('UserLog', cascade='all')
206 204
207 205 def __repr__(self):
208 206 return "<%s('%s:%s')>" % (self.__class__.__name__,
209 207 self.repo_id, self.repo_name)
210 208
211 209 @classmethod
212 210 def by_repo_name(cls, repo_name):
213 211 return Session.query(cls).filter(cls.repo_name == repo_name).one()
214 212
215 213 @property
216 214 def just_name(self):
217 215 return self.repo_name.split(os.sep)[-1]
218 216
219 217 @property
220 218 def groups_with_parents(self):
221 219 groups = []
222 220 if self.group is None:
223 221 return groups
224 222
225 223 cur_gr = self.group
226 224 groups.insert(0, cur_gr)
227 225 while 1:
228 226 gr = getattr(cur_gr, 'parent_group', None)
229 227 cur_gr = cur_gr.parent_group
230 228 if gr is None:
231 229 break
232 230 groups.insert(0, gr)
233 231
234 232 return groups
235 233
236 234 @property
237 235 def groups_and_repo(self):
238 236 return self.groups_with_parents, self.just_name
239 237
240 238
241 239 class Group(Base):
242 240 __tablename__ = 'groups'
243 241 __table_args__ = (UniqueConstraint('group_name'), {'useexisting':True},)
244 242
245 243 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
246 244 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
247 245 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
248 246
249 247 parent_group = relationship('Group', remote_side=group_id)
250 248
251 249
252 250 def __init__(self, group_name='', parent_group=None):
253 251 self.group_name = group_name
254 252 self.parent_group = parent_group
255 253
256 254 def __repr__(self):
257 255 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
258 256 self.group_name)
259 257
260 258 @property
261 259 def parents(self):
262 260 groups = []
263 261 if self.parent_group is None:
264 262 return groups
265 263 cur_gr = self.parent_group
266 264 groups.insert(0, cur_gr)
267 265 while 1:
268 266 gr = getattr(cur_gr, 'parent_group', None)
269 267 cur_gr = cur_gr.parent_group
270 268 if gr is None:
271 269 break
272 270 groups.insert(0, gr)
273 271 return groups
274 272
275 273 @property
276 274 def repositories(self):
277 275 return Session.query(Repository).filter(Repository.group == self).all()
278 276
279 277 class Permission(Base):
280 278 __tablename__ = 'permissions'
281 279 __table_args__ = {'useexisting':True}
282 280 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 281 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
284 282 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
285 283
286 284 def __repr__(self):
287 285 return "<%s('%s:%s')>" % (self.__class__.__name__,
288 286 self.permission_id, self.permission_name)
289 287
290 288 class RepoToPerm(Base):
291 289 __tablename__ = 'repo_to_perm'
292 290 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
293 291 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
294 292 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
295 293 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
296 294 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
297 295
298 296 user = relationship('User')
299 297 permission = relationship('Permission')
300 298 repository = relationship('Repository')
301 299
302 300 class UserToPerm(Base):
303 301 __tablename__ = 'user_to_perm'
304 302 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
305 303 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
306 304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
307 305 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
308 306
309 307 user = relationship('User')
310 308 permission = relationship('Permission')
311 309
312 310
313 311 class UsersGroupToPerm(Base):
314 312 __tablename__ = 'users_group_to_perm'
315 313 __table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True})
316 314 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
317 315 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
318 316 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
319 317 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
320 318
321 319 users_group = relationship('UsersGroup')
322 320 permission = relationship('Permission')
323 321 repository = relationship('Repository')
324 322
325 323 class GroupToPerm(Base):
326 324 __tablename__ = 'group_to_perm'
327 325 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'useexisting':True})
328 326
329 327 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
330 328 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
331 329 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
332 330 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
333 331
334 332 user = relationship('User')
335 333 permission = relationship('Permission')
336 334 group = relationship('Group')
337 335
338 336 class Statistics(Base):
339 337 __tablename__ = 'statistics'
340 338 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
341 339 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
342 340 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
343 341 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
344 342 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
345 343 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
346 344 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
347 345
348 346 repository = relationship('Repository', single_parent=True)
349 347
350 348 class UserFollowing(Base):
351 349 __tablename__ = 'user_followings'
352 350 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
353 351 UniqueConstraint('user_id', 'follows_user_id')
354 352 , {'useexisting':True})
355 353
356 354 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
357 355 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
358 356 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
359 357 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
360 358
361 359 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
362 360
363 361 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
364 362 follows_repository = relationship('Repository', order_by='Repository.repo_name')
365 363
366 364 class CacheInvalidation(Base):
367 365 __tablename__ = 'cache_invalidation'
368 366 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
369 367 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
370 368 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
371 369 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
372 370 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
373 371
374 372
375 373 def __init__(self, cache_key, cache_args=''):
376 374 self.cache_key = cache_key
377 375 self.cache_args = cache_args
378 376 self.cache_active = False
379 377
380 378 def __repr__(self):
381 379 return "<%s('%s:%s')>" % (self.__class__.__name__,
382 380 self.cache_id, self.cache_key)
383 381
384 382 class DbMigrateVersion(Base):
385 383 __tablename__ = 'db_migrate_version'
386 384 __table_args__ = {'useexisting':True}
387 385 repository_id = Column('repository_id', String(250), primary_key=True)
388 386 repository_path = Column('repository_path', Text)
389 387 version = Column('version', Integer)
@@ -1,114 +1,112 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.permission
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 permissions model for RhodeCode
7 7
8 8 :created_on: Aug 20, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 import traceback
30 28
31 29 from sqlalchemy.exc import DatabaseError
32 30
33 31 from rhodecode.model import BaseModel
34 32 from rhodecode.model.db import User, Permission, UserToPerm, RepoToPerm
35 33 from rhodecode.model.caching_query import FromCache
36 34
37 35 log = logging.getLogger(__name__)
38 36
39 37
40 38 class PermissionModel(BaseModel):
41 39 """Permissions model for RhodeCode
42 40 """
43 41
44 42 def get_permission(self, permission_id, cache=False):
45 43 """Get's permissions by id
46 44
47 45 :param permission_id: id of permission to get from database
48 46 :param cache: use Cache for this query
49 47 """
50 48 perm = self.sa.query(Permission)
51 49 if cache:
52 50 perm = perm.options(FromCache("sql_cache_short",
53 51 "get_permission_%s" % permission_id))
54 52 return perm.get(permission_id)
55 53
56 54 def get_permission_by_name(self, name, cache=False):
57 55 """Get's permissions by given name
58 56
59 57 :param name: name to fetch
60 58 :param cache: Use cache for this query
61 59 """
62 60 perm = self.sa.query(Permission)\
63 61 .filter(Permission.permission_name == name)
64 62 if cache:
65 63 perm = perm.options(FromCache("sql_cache_short",
66 64 "get_permission_%s" % name))
67 65 return perm.scalar()
68 66
69 67 def update(self, form_result):
70 68 perm_user = self.sa.query(User)\
71 69 .filter(User.username == form_result['perm_user_name']).scalar()
72 70 u2p = self.sa.query(UserToPerm).filter(UserToPerm.user == perm_user).all()
73 71 if len(u2p) != 3:
74 72 raise Exception('Defined: %s should be 3 permissions for default'
75 73 ' user. This should not happen please verify'
76 74 ' your database' % len(u2p))
77 75
78 76 try:
79 77 #stage 1 change defaults
80 78 for p in u2p:
81 79 if p.permission.permission_name.startswith('repository.'):
82 80 p.permission = self.get_permission_by_name(
83 81 form_result['default_perm'])
84 82 self.sa.add(p)
85 83
86 84 if p.permission.permission_name.startswith('hg.register.'):
87 85 p.permission = self.get_permission_by_name(
88 86 form_result['default_register'])
89 87 self.sa.add(p)
90 88
91 89 if p.permission.permission_name.startswith('hg.create.'):
92 90 p.permission = self.get_permission_by_name(
93 91 form_result['default_create'])
94 92 self.sa.add(p)
95 93
96 94 #stage 2 update all default permissions for repos if checked
97 95 if form_result['overwrite_default'] == True:
98 96 for r2p in self.sa.query(RepoToPerm)\
99 97 .filter(RepoToPerm.user == perm_user).all():
100 98 r2p.permission = self.get_permission_by_name(
101 99 form_result['default_perm'])
102 100 self.sa.add(r2p)
103 101
104 102 #stage 3 set anonymous access
105 103 if perm_user.username == 'default':
106 104 perm_user.active = bool(form_result['anonymous'])
107 105 self.sa.add(perm_user)
108 106
109 107
110 108 self.sa.commit()
111 109 except (DatabaseError,):
112 110 log.error(traceback.format_exc())
113 111 self.sa.rollback()
114 112 raise
@@ -1,354 +1,352 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.repo
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository model for rhodecode
7 7
8 8 :created_on: Jun 5, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25 import os
28 26 import shutil
29 27 import logging
30 28 import traceback
31 29 from datetime import datetime
32 30
33 31 from sqlalchemy.orm import joinedload, make_transient
34 32
35 33 from vcs.utils.lazy import LazyProperty
36 34 from vcs.backends import get_backend
37 35
38 36 from rhodecode.model import BaseModel
39 37 from rhodecode.model.caching_query import FromCache
40 38 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
41 39 Statistics, UsersGroup, UsersGroupToPerm, RhodeCodeUi
42 40 from rhodecode.model.user import UserModel
43 41 from rhodecode.model.users_group import UsersGroupMember, UsersGroupModel
44 42
45 43
46 44 log = logging.getLogger(__name__)
47 45
48 46 class RepoModel(BaseModel):
49 47
50 48 @LazyProperty
51 49 def repos_path(self):
52 50 """Get's the repositories root path from database
53 51 """
54 52
55 53 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
56 54 return q.ui_value
57 55
58 56 def get(self, repo_id, cache=False):
59 57 repo = self.sa.query(Repository)\
60 58 .filter(Repository.repo_id == repo_id)
61 59
62 60 if cache:
63 61 repo = repo.options(FromCache("sql_cache_short",
64 62 "get_repo_%s" % repo_id))
65 63 return repo.scalar()
66 64
67 65
68 66 def get_by_repo_name(self, repo_name, cache=False):
69 67 repo = self.sa.query(Repository)\
70 68 .filter(Repository.repo_name == repo_name)
71 69
72 70 if cache:
73 71 repo = repo.options(FromCache("sql_cache_short",
74 72 "get_repo_%s" % repo_name))
75 73 return repo.scalar()
76 74
77 75
78 76 def get_full(self, repo_name, cache=False, invalidate=False):
79 77 repo = self.sa.query(Repository)\
80 78 .options(joinedload(Repository.fork))\
81 79 .options(joinedload(Repository.user))\
82 80 .filter(Repository.repo_name == repo_name)\
83 81
84 82 if cache:
85 83 repo = repo.options(FromCache("sql_cache_long",
86 84 "get_repo_full_%s" % repo_name))
87 85 if invalidate and cache:
88 86 repo.invalidate()
89 87
90 88 ret = repo.scalar()
91 89
92 90 #make transient for sake of errors
93 91 make_transient(ret)
94 92 for k in ['fork', 'user']:
95 93 attr = getattr(ret, k, False)
96 94 if attr:
97 95 make_transient(attr)
98 96 return ret
99 97
100 98
101 99 def get_users_js(self):
102 100
103 101 users = self.sa.query(User).filter(User.active == True).all()
104 102 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
105 103 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
106 104 u.lastname, u.username)
107 105 for u in users])
108 106 return users_array
109 107
110 108
111 109 def get_users_groups_js(self):
112 110 users_groups = self.sa.query(UsersGroup)\
113 111 .filter(UsersGroup.users_group_active == True).all()
114 112
115 113 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
116 114
117 115 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
118 116 (gr.users_group_id, gr.users_group_name,
119 117 len(gr.members))
120 118 for gr in users_groups])
121 119 return users_groups_array
122 120
123 121 def update(self, repo_name, form_data):
124 122 try:
125 123 cur_repo = self.get_by_repo_name(repo_name, cache=False)
126 124 user_model = UserModel(self.sa)
127 125 users_group_model = UsersGroupModel(self.sa)
128 126
129 127 #update permissions
130 128 for member, perm, member_type in form_data['perms_updates']:
131 129 if member_type == 'user':
132 130 r2p = self.sa.query(RepoToPerm)\
133 131 .filter(RepoToPerm.user == user_model.get_by_username(member))\
134 132 .filter(RepoToPerm.repository == cur_repo)\
135 133 .one()
136 134
137 135 r2p.permission = self.sa.query(Permission)\
138 136 .filter(Permission.permission_name == perm)\
139 137 .scalar()
140 138 self.sa.add(r2p)
141 139 else:
142 140 g2p = self.sa.query(UsersGroupToPerm)\
143 141 .filter(UsersGroupToPerm.users_group == users_group_model.get_by_groupname(member))\
144 142 .filter(UsersGroupToPerm.repository == cur_repo)\
145 143 .one()
146 144
147 145 g2p.permission = self.sa.query(Permission)\
148 146 .filter(Permission.permission_name == perm)\
149 147 .scalar()
150 148 self.sa.add(g2p)
151 149
152 150 #set new permissions
153 151 for member, perm, member_type in form_data['perms_new']:
154 152 if member_type == 'user':
155 153 r2p = RepoToPerm()
156 154 r2p.repository = cur_repo
157 155 r2p.user = user_model.get_by_username(member)
158 156
159 157 r2p.permission = self.sa.query(Permission)\
160 158 .filter(Permission.permission_name == perm)\
161 159 .scalar()
162 160 self.sa.add(r2p)
163 161 else:
164 162 g2p = UsersGroupToPerm()
165 163 g2p.repository = cur_repo
166 164 g2p.users_group = users_group_model.get_by_groupname(member)
167 165
168 166 g2p.permission = self.sa.query(Permission)\
169 167 .filter(Permission.permission_name == perm)\
170 168 .scalar()
171 169 self.sa.add(g2p)
172 170
173 171 #update current repo
174 172 for k, v in form_data.items():
175 173 if k == 'user':
176 174 cur_repo.user = user_model.get(v)
177 175 else:
178 176 setattr(cur_repo, k, v)
179 177
180 178 self.sa.add(cur_repo)
181 179
182 180 if repo_name != form_data['repo_name']:
183 181 #rename our data
184 182 self.__rename_repo(repo_name, form_data['repo_name'])
185 183
186 184 self.sa.commit()
187 185 except:
188 186 log.error(traceback.format_exc())
189 187 self.sa.rollback()
190 188 raise
191 189
192 190 def create(self, form_data, cur_user, just_db=False, fork=False):
193 191 try:
194 192 if fork:
195 193 #force str since hg doesn't go with unicode
196 194 repo_name = str(form_data['fork_name'])
197 195 org_name = str(form_data['repo_name'])
198 196
199 197 else:
200 198 org_name = repo_name = str(form_data['repo_name'])
201 199 new_repo = Repository()
202 200 new_repo.enable_statistics = False
203 201 for k, v in form_data.items():
204 202 if k == 'repo_name':
205 203 v = repo_name
206 204 setattr(new_repo, k, v)
207 205
208 206 if fork:
209 207 parent_repo = self.sa.query(Repository)\
210 208 .filter(Repository.repo_name == org_name).scalar()
211 209 new_repo.fork = parent_repo
212 210
213 211 new_repo.user_id = cur_user.user_id
214 212 self.sa.add(new_repo)
215 213
216 214 #create default permission
217 215 repo_to_perm = RepoToPerm()
218 216 default = 'repository.read'
219 217 for p in UserModel(self.sa).get_by_username('default',
220 218 cache=False).user_perms:
221 219 if p.permission.permission_name.startswith('repository.'):
222 220 default = p.permission.permission_name
223 221 break
224 222
225 223 default_perm = 'repository.none' if form_data['private'] else default
226 224
227 225 repo_to_perm.permission_id = self.sa.query(Permission)\
228 226 .filter(Permission.permission_name == default_perm)\
229 227 .one().permission_id
230 228
231 229 repo_to_perm.repository = new_repo
232 230 repo_to_perm.user_id = UserModel(self.sa)\
233 231 .get_by_username('default', cache=False).user_id
234 232
235 233 self.sa.add(repo_to_perm)
236 234
237 235 if not just_db:
238 236 self.__create_repo(repo_name, form_data['repo_type'],
239 237 form_data['clone_uri'])
240 238
241 239 self.sa.commit()
242 240
243 241 #now automatically start following this repository as owner
244 242 from rhodecode.model.scm import ScmModel
245 243 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
246 244 cur_user.user_id)
247 245
248 246 except:
249 247 log.error(traceback.format_exc())
250 248 self.sa.rollback()
251 249 raise
252 250
253 251 def create_fork(self, form_data, cur_user):
254 252 from rhodecode.lib.celerylib import tasks, run_task
255 253 run_task(tasks.create_repo_fork, form_data, cur_user)
256 254
257 255 def delete(self, repo):
258 256 try:
259 257 self.sa.delete(repo)
260 258 self.__delete_repo(repo)
261 259 self.sa.commit()
262 260 except:
263 261 log.error(traceback.format_exc())
264 262 self.sa.rollback()
265 263 raise
266 264
267 265 def delete_perm_user(self, form_data, repo_name):
268 266 try:
269 267 self.sa.query(RepoToPerm)\
270 268 .filter(RepoToPerm.repository \
271 269 == self.get_by_repo_name(repo_name))\
272 270 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
273 271 self.sa.commit()
274 272 except:
275 273 log.error(traceback.format_exc())
276 274 self.sa.rollback()
277 275 raise
278 276
279 277 def delete_perm_users_group(self, form_data, repo_name):
280 278 try:
281 279 self.sa.query(UsersGroupToPerm)\
282 280 .filter(UsersGroupToPerm.repository \
283 281 == self.get_by_repo_name(repo_name))\
284 282 .filter(UsersGroupToPerm.users_group_id \
285 283 == form_data['users_group_id']).delete()
286 284 self.sa.commit()
287 285 except:
288 286 log.error(traceback.format_exc())
289 287 self.sa.rollback()
290 288 raise
291 289
292 290 def delete_stats(self, repo_name):
293 291 try:
294 292 self.sa.query(Statistics)\
295 293 .filter(Statistics.repository == \
296 294 self.get_by_repo_name(repo_name)).delete()
297 295 self.sa.commit()
298 296 except:
299 297 log.error(traceback.format_exc())
300 298 self.sa.rollback()
301 299 raise
302 300
303 301
304 302 def __create_repo(self, repo_name, alias, clone_uri=False):
305 303 """
306 304 makes repository on filesystem
307 305
308 306 :param repo_name:
309 307 :param alias:
310 308 """
311 309 from rhodecode.lib.utils import check_repo
312 310 repo_path = os.path.join(self.repos_path, repo_name)
313 311 if check_repo(repo_name, self.repos_path):
314 312 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
315 313 clone_uri)
316 314 backend = get_backend(alias)
317 315 backend(repo_path, create=True, src_url=clone_uri)
318 316
319 317 def __rename_repo(self, old, new):
320 318 """
321 319 renames repository on filesystem
322 320
323 321 :param old: old name
324 322 :param new: new name
325 323 """
326 324 log.info('renaming repo from %s to %s', old, new)
327 325
328 326 old_path = os.path.join(self.repos_path, old)
329 327 new_path = os.path.join(self.repos_path, new)
330 328 if os.path.isdir(new_path):
331 329 raise Exception('Was trying to rename to already existing dir %s',
332 330 new_path)
333 331 shutil.move(old_path, new_path)
334 332
335 333 def __delete_repo(self, repo):
336 334 """
337 335 removes repo from filesystem, the removal is acctually made by
338 336 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
339 337 repository is no longer valid for rhodecode, can be undeleted later on
340 338 by reverting the renames on this repository
341 339
342 340 :param repo: repo object
343 341 """
344 342 rm_path = os.path.join(self.repos_path, repo.repo_name)
345 343 log.info("Removing %s", rm_path)
346 344 #disable hg/git
347 345 alias = repo.repo_type
348 346 shutil.move(os.path.join(rm_path, '.%s' % alias),
349 347 os.path.join(rm_path, 'rm__.%s' % alias))
350 348 #disable repo
351 349 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
352 350 % (datetime.today()\
353 351 .strftime('%Y%m%d_%H%M%S_%f'),
354 352 repo.repo_name)))
@@ -1,412 +1,410 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25 import os
28 26 import time
29 27 import traceback
30 28 import logging
31 29
32 30 from mercurial import ui
33 31
34 32 from sqlalchemy.exc import DatabaseError
35 33 from sqlalchemy.orm import make_transient
36 34
37 35 from beaker.cache import cache_region, region_invalidate
38 36
39 37 from vcs import get_backend
40 38 from vcs.utils.helpers import get_scm
41 39 from vcs.exceptions import RepositoryError, VCSError
42 40 from vcs.utils.lazy import LazyProperty
43 41
44 42 from rhodecode import BACKENDS
45 43 from rhodecode.lib import helpers as h
46 44 from rhodecode.lib.auth import HasRepoPermissionAny
47 45 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
48 46 action_logger
49 47 from rhodecode.model import BaseModel
50 48 from rhodecode.model.user import UserModel
51 49 from rhodecode.model.repo import RepoModel
52 50 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
53 51 UserFollowing, UserLog
54 52 from rhodecode.model.caching_query import FromCache
55 53
56 54 log = logging.getLogger(__name__)
57 55
58 56
59 57 class UserTemp(object):
60 58 def __init__(self, user_id):
61 59 self.user_id = user_id
62 60
63 61 def __repr__(self):
64 62 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
65 63
66 64 class RepoTemp(object):
67 65 def __init__(self, repo_id):
68 66 self.repo_id = repo_id
69 67
70 68 def __repr__(self):
71 69 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72 70
73 71 class ScmModel(BaseModel):
74 72 """Generic Scm Model
75 73 """
76 74
77 75 @LazyProperty
78 76 def repos_path(self):
79 77 """Get's the repositories root path from database
80 78 """
81 79
82 80 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
83 81
84 82 return q.ui_value
85 83
86 84 def repo_scan(self, repos_path=None):
87 85 """Listing of repositories in given path. This path should not be a
88 86 repository itself. Return a dictionary of repository objects
89 87
90 88 :param repos_path: path to directory containing repositories
91 89 """
92 90
93 91 log.info('scanning for repositories in %s', repos_path)
94 92
95 93 if repos_path is None:
96 94 repos_path = self.repos_path
97 95
98 96 baseui = make_ui('db')
99 97 repos_list = {}
100 98
101 99 for name, path in get_filesystem_repos(repos_path, recursive=True):
102 100 try:
103 101 if repos_list.has_key(name):
104 102 raise RepositoryError('Duplicate repository name %s '
105 103 'found in %s' % (name, path))
106 104 else:
107 105
108 106 klass = get_backend(path[0])
109 107
110 108 if path[0] == 'hg' and path[0] in BACKENDS.keys():
111 109 repos_list[name] = klass(path[1], baseui=baseui)
112 110
113 111 if path[0] == 'git' and path[0] in BACKENDS.keys():
114 112 repos_list[name] = klass(path[1])
115 113 except OSError:
116 114 continue
117 115
118 116 return repos_list
119 117
120 118 def get_repos(self, all_repos=None):
121 119 """Get all repos from db and for each repo create it's backend instance.
122 120 and fill that backed with information from database
123 121
124 122 :param all_repos: give specific repositories list, good for filtering
125 123 this have to be a list of just the repository names
126 124 """
127 125 if all_repos is None:
128 126 repos = self.sa.query(Repository)\
129 127 .order_by(Repository.repo_name).all()
130 128 all_repos = [r.repo_name for r in repos]
131 129
132 130 #get the repositories that should be invalidated
133 131 invalidation_list = [str(x.cache_key) for x in \
134 132 self.sa.query(CacheInvalidation.cache_key)\
135 133 .filter(CacheInvalidation.cache_active == False)\
136 134 .all()]
137 135 for r_name in all_repos:
138 136 r_dbr = self.get(r_name, invalidation_list)
139 137 if r_dbr is not None:
140 138 repo, dbrepo = r_dbr
141 139
142 140 last_change = repo.last_change
143 141 tip = h.get_changeset_safe(repo, 'tip')
144 142
145 143 tmp_d = {}
146 144 tmp_d['name'] = dbrepo.repo_name
147 145 tmp_d['name_sort'] = tmp_d['name'].lower()
148 146 tmp_d['description'] = dbrepo.description
149 147 tmp_d['description_sort'] = tmp_d['description']
150 148 tmp_d['last_change'] = last_change
151 149 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
152 150 tmp_d['tip'] = tip.raw_id
153 151 tmp_d['tip_sort'] = tip.revision
154 152 tmp_d['rev'] = tip.revision
155 153 tmp_d['contact'] = dbrepo.user.full_contact
156 154 tmp_d['contact_sort'] = tmp_d['contact']
157 155 tmp_d['owner_sort'] = tmp_d['contact']
158 156 tmp_d['repo_archives'] = list(repo._get_archives())
159 157 tmp_d['last_msg'] = tip.message
160 158 tmp_d['repo'] = repo
161 159 tmp_d['dbrepo'] = dbrepo.get_dict()
162 160 tmp_d['dbrepo_fork'] = dbrepo.fork.get_dict() if dbrepo.fork else {}
163 161 yield tmp_d
164 162
165 163 def get(self, repo_name, invalidation_list=None, retval='all'):
166 164 """Returns a tuple of Repository,DbRepository,
167 165 Get's repository from given name, creates BackendInstance and
168 166 propagates it's data from database with all additional information
169 167
170 168 :param repo_name:
171 169 :param invalidation_list: if a invalidation list is given the get
172 170 method should not manually check if this repository needs
173 171 invalidation and just invalidate the repositories in list
174 172 :param retval: string specifing what to return one of 'repo','dbrepo',
175 173 'all'if repo or dbrepo is given it'll just lazy load chosen type
176 174 and return None as the second
177 175 """
178 176 if not HasRepoPermissionAny('repository.read', 'repository.write',
179 177 'repository.admin')(repo_name, 'get repo check'):
180 178 return
181 179
182 180 #======================================================================
183 181 # CACHE FUNCTION
184 182 #======================================================================
185 183 @cache_region('long_term')
186 184 def _get_repo(repo_name):
187 185
188 186 repo_path = os.path.join(self.repos_path, repo_name)
189 187
190 188 try:
191 189 alias = get_scm(repo_path)[0]
192 190 log.debug('Creating instance of %s repository', alias)
193 191 backend = get_backend(alias)
194 192 except VCSError:
195 193 log.error(traceback.format_exc())
196 194 log.error('Perhaps this repository is in db and not in '
197 195 'filesystem run rescan repositories with '
198 196 '"destroy old data " option from admin panel')
199 197 return
200 198
201 199 if alias == 'hg':
202 200 repo = backend(repo_path, create=False, baseui=make_ui('db'))
203 201 #skip hidden web repository
204 202 if repo._get_hidden():
205 203 return
206 204 else:
207 205 repo = backend(repo_path, create=False)
208 206
209 207 return repo
210 208
211 209 pre_invalidate = True
212 210 dbinvalidate = False
213 211
214 212 if invalidation_list is not None:
215 213 pre_invalidate = repo_name in invalidation_list
216 214
217 215 if pre_invalidate:
218 216 #this returns object to invalidate
219 217 invalidate = self._should_invalidate(repo_name)
220 218 if invalidate:
221 219 log.info('invalidating cache for repository %s', repo_name)
222 220 region_invalidate(_get_repo, None, repo_name)
223 221 self._mark_invalidated(invalidate)
224 222 dbinvalidate = True
225 223
226 224 r, dbr = None, None
227 225 if retval == 'repo' or 'all':
228 226 r = _get_repo(repo_name)
229 227 if retval == 'dbrepo' or 'all':
230 228 dbr = RepoModel().get_full(repo_name, cache=True,
231 229 invalidate=dbinvalidate)
232 230
233 231
234 232 return r, dbr
235 233
236 234 def mark_for_invalidation(self, repo_name):
237 235 """Puts cache invalidation task into db for
238 236 further global cache invalidation
239 237
240 238 :param repo_name: this repo that should invalidation take place
241 239 """
242 240
243 241 log.debug('marking %s for invalidation', repo_name)
244 242 cache = self.sa.query(CacheInvalidation)\
245 243 .filter(CacheInvalidation.cache_key == repo_name).scalar()
246 244
247 245 if cache:
248 246 #mark this cache as inactive
249 247 cache.cache_active = False
250 248 else:
251 249 log.debug('cache key not found in invalidation db -> creating one')
252 250 cache = CacheInvalidation(repo_name)
253 251
254 252 try:
255 253 self.sa.add(cache)
256 254 self.sa.commit()
257 255 except (DatabaseError,):
258 256 log.error(traceback.format_exc())
259 257 self.sa.rollback()
260 258
261 259
262 260 def toggle_following_repo(self, follow_repo_id, user_id):
263 261
264 262 f = self.sa.query(UserFollowing)\
265 263 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
266 264 .filter(UserFollowing.user_id == user_id).scalar()
267 265
268 266 if f is not None:
269 267
270 268 try:
271 269 self.sa.delete(f)
272 270 self.sa.commit()
273 271 action_logger(UserTemp(user_id),
274 272 'stopped_following_repo',
275 273 RepoTemp(follow_repo_id))
276 274 return
277 275 except:
278 276 log.error(traceback.format_exc())
279 277 self.sa.rollback()
280 278 raise
281 279
282 280
283 281 try:
284 282 f = UserFollowing()
285 283 f.user_id = user_id
286 284 f.follows_repo_id = follow_repo_id
287 285 self.sa.add(f)
288 286 self.sa.commit()
289 287 action_logger(UserTemp(user_id),
290 288 'started_following_repo',
291 289 RepoTemp(follow_repo_id))
292 290 except:
293 291 log.error(traceback.format_exc())
294 292 self.sa.rollback()
295 293 raise
296 294
297 295 def toggle_following_user(self, follow_user_id , user_id):
298 296 f = self.sa.query(UserFollowing)\
299 297 .filter(UserFollowing.follows_user_id == follow_user_id)\
300 298 .filter(UserFollowing.user_id == user_id).scalar()
301 299
302 300 if f is not None:
303 301 try:
304 302 self.sa.delete(f)
305 303 self.sa.commit()
306 304 return
307 305 except:
308 306 log.error(traceback.format_exc())
309 307 self.sa.rollback()
310 308 raise
311 309
312 310 try:
313 311 f = UserFollowing()
314 312 f.user_id = user_id
315 313 f.follows_user_id = follow_user_id
316 314 self.sa.add(f)
317 315 self.sa.commit()
318 316 except:
319 317 log.error(traceback.format_exc())
320 318 self.sa.rollback()
321 319 raise
322 320
323 321 def is_following_repo(self, repo_name, user_id, cache=False):
324 322 r = self.sa.query(Repository)\
325 323 .filter(Repository.repo_name == repo_name).scalar()
326 324
327 325 f = self.sa.query(UserFollowing)\
328 326 .filter(UserFollowing.follows_repository == r)\
329 327 .filter(UserFollowing.user_id == user_id).scalar()
330 328
331 329 return f is not None
332 330
333 331 def is_following_user(self, username, user_id, cache=False):
334 332 u = UserModel(self.sa).get_by_username(username)
335 333
336 334 f = self.sa.query(UserFollowing)\
337 335 .filter(UserFollowing.follows_user == u)\
338 336 .filter(UserFollowing.user_id == user_id).scalar()
339 337
340 338 return f is not None
341 339
342 340 def get_followers(self, repo_id):
343 341 if isinstance(repo_id, int):
344 342 return self.sa.query(UserFollowing)\
345 343 .filter(UserFollowing.follows_repo_id == repo_id).count()
346 344 else:
347 345 return self.sa.query(UserFollowing)\
348 346 .filter(UserFollowing.follows_repository \
349 347 == RepoModel().get_by_repo_name(repo_id)).count()
350 348
351 349 def get_forks(self, repo_id):
352 350 if isinstance(repo_id, int):
353 351 return self.sa.query(Repository)\
354 352 .filter(Repository.fork_id == repo_id).count()
355 353 else:
356 354 return self.sa.query(Repository)\
357 355 .filter(Repository.fork \
358 356 == RepoModel().get_by_repo_name(repo_id)).count()
359 357
360 358
361 359 def pull_changes(self, repo_name, username):
362 360 repo, dbrepo = self.get(repo_name, retval='all')
363 361
364 362 try:
365 363 extras = {'ip':'',
366 364 'username':username,
367 365 'action':'push_remote',
368 366 'repository':repo_name}
369 367
370 368 #inject ui extra param to log this action via push logger
371 369 for k, v in extras.items():
372 370 repo._repo.ui.setconfig('rhodecode_extras', k, v)
373 371
374 372 repo.pull(dbrepo.clone_uri)
375 373 self.mark_for_invalidation(repo_name)
376 374 except:
377 375 log.error(traceback.format_exc())
378 376 raise
379 377
380 378 def get_unread_journal(self):
381 379 return self.sa.query(UserLog).count()
382 380
383 381
384 382 def _should_invalidate(self, repo_name):
385 383 """Looks up database for invalidation signals for this repo_name
386 384
387 385 :param repo_name:
388 386 """
389 387
390 388 ret = self.sa.query(CacheInvalidation)\
391 389 .filter(CacheInvalidation.cache_key == repo_name)\
392 390 .filter(CacheInvalidation.cache_active == False)\
393 391 .scalar()
394 392
395 393 return ret
396 394
397 395 def _mark_invalidated(self, cache_key):
398 396 """ Marks all occurrences of cache to invalidation as already
399 397 invalidated
400 398
401 399 :param cache_key:
402 400 """
403 401
404 402 if cache_key:
405 403 log.debug('marking %s as already invalidated', cache_key)
406 404 try:
407 405 cache_key.cache_active = True
408 406 self.sa.add(cache_key)
409 407 self.sa.commit()
410 408 except (DatabaseError,):
411 409 log.error(traceback.format_exc())
412 410 self.sa.rollback()
@@ -1,105 +1,103 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Settings model for RhodeCode
7 7
8 8 :created on Nov 17, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27
30 28 from rhodecode.model import BaseModel
31 29 from rhodecode.model.caching_query import FromCache
32 30 from rhodecode.model.db import RhodeCodeSettings
33 31
34 32 log = logging.getLogger(__name__)
35 33
36 34 class SettingsModel(BaseModel):
37 35 """
38 36 Settings model
39 37 """
40 38
41 39 def get(self, settings_key, cache=False):
42 40 r = self.sa.query(RhodeCodeSettings)\
43 41 .filter(RhodeCodeSettings.app_settings_name == settings_key).scalar()
44 42 if cache:
45 43 r = r.options(FromCache("sql_cache_short",
46 44 "get_setting_%s" % settings_key))
47 45 return r
48 46
49 47 def get_app_settings(self, cache=False):
50 48 """Get's config from database, each config key is prefixed with
51 49 'rhodecode_' prefix, than global pylons config is updated with such
52 50 keys
53 51 """
54 52
55 53 ret = self.sa.query(RhodeCodeSettings)
56 54
57 55 if cache:
58 56 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
59 57
60 58 if not ret:
61 59 raise Exception('Could not get application settings !')
62 60 settings = {}
63 61 for each in ret:
64 62 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
65 63
66 64 return settings
67 65
68 66 def get_ldap_settings(self):
69 67 """
70 68 Returns ldap settings from database
71 69 :returns:
72 70 ldap_active
73 71 ldap_host
74 72 ldap_port
75 73 ldap_ldaps
76 74 ldap_tls_reqcert
77 75 ldap_dn_user
78 76 ldap_dn_pass
79 77 ldap_base_dn
80 78 ldap_filter
81 79 ldap_search_scope
82 80 ldap_attr_login
83 81 ldap_attr_firstname
84 82 ldap_attr_lastname
85 83 ldap_attr_email
86 84 """
87 85 # ldap_search_scope
88 86
89 87 r = self.sa.query(RhodeCodeSettings)\
90 88 .filter(RhodeCodeSettings.app_settings_name\
91 89 .startswith('ldap_'))\
92 90 .all()
93 91
94 92 fd = {}
95 93
96 94 for row in r:
97 95 v = row.app_settings_value
98 96 if v in ['true', 'yes', 'on', 'y', 't', '1']:
99 97 v = True
100 98 elif v in ['false', 'no', 'off', 'n', 'f', '0']:
101 99 v = False
102 100
103 101 fd.update({row.app_settings_name:v})
104 102
105 103 return fd
@@ -1,344 +1,342 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 import traceback
30 28
31 29 from pylons.i18n.translation import _
32 30
33 31 from rhodecode.model import BaseModel
34 32 from rhodecode.model.caching_query import FromCache
35 33 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
36 34 UserToPerm, UsersGroupToPerm, UsersGroupMember
37 35 from rhodecode.lib.exceptions import DefaultUserException, UserOwnsReposException
38 36
39 37 from sqlalchemy.exc import DatabaseError
40 38 from rhodecode.lib import generate_api_key
41 39
42 40 log = logging.getLogger(__name__)
43 41
44 42 PERM_WEIGHTS = {'repository.none':0,
45 43 'repository.read':1,
46 44 'repository.write':3,
47 45 'repository.admin':3}
48 46
49 47 class UserModel(BaseModel):
50 48
51 49 def get(self, user_id, cache=False):
52 50 user = self.sa.query(User)
53 51 if cache:
54 52 user = user.options(FromCache("sql_cache_short",
55 53 "get_user_%s" % user_id))
56 54 return user.get(user_id)
57 55
58 56
59 57 def get_by_username(self, username, cache=False, case_insensitive=False):
60 58
61 59 if case_insensitive:
62 60 user = self.sa.query(User).filter(User.username.ilike(username))
63 61 else:
64 62 user = self.sa.query(User)\
65 63 .filter(User.username == username)
66 64 if cache:
67 65 user = user.options(FromCache("sql_cache_short",
68 66 "get_user_%s" % username))
69 67 return user.scalar()
70 68
71 69
72 70 def get_by_api_key(self, api_key, cache=False):
73 71
74 72 user = self.sa.query(User)\
75 73 .filter(User.api_key == api_key)
76 74 if cache:
77 75 user = user.options(FromCache("sql_cache_short",
78 76 "get_user_%s" % api_key))
79 77 return user.scalar()
80 78
81 79 def create(self, form_data):
82 80 try:
83 81 new_user = User()
84 82 for k, v in form_data.items():
85 83 setattr(new_user, k, v)
86 84
87 85 new_user.api_key = generate_api_key(form_data['username'])
88 86 self.sa.add(new_user)
89 87 self.sa.commit()
90 88 except:
91 89 log.error(traceback.format_exc())
92 90 self.sa.rollback()
93 91 raise
94 92
95 93 def create_ldap(self, username, password, user_dn, attrs):
96 94 """
97 95 Checks if user is in database, if not creates this user marked
98 96 as ldap user
99 97 :param username:
100 98 :param password:
101 99 :param user_dn:
102 100 :param attrs:
103 101 """
104 102 from rhodecode.lib.auth import get_crypt_password
105 103 log.debug('Checking for such ldap account in RhodeCode database')
106 104 if self.get_by_username(username, case_insensitive=True) is None:
107 105 try:
108 106 new_user = User()
109 107 new_user.username = username.lower() # add ldap account always lowercase
110 108 new_user.password = get_crypt_password(password)
111 109 new_user.api_key = generate_api_key(username)
112 110 new_user.email = attrs['email']
113 111 new_user.active = True
114 112 new_user.ldap_dn = user_dn
115 113 new_user.name = attrs['name']
116 114 new_user.lastname = attrs['lastname']
117 115
118 116
119 117 self.sa.add(new_user)
120 118 self.sa.commit()
121 119 return True
122 120 except (DatabaseError,):
123 121 log.error(traceback.format_exc())
124 122 self.sa.rollback()
125 123 raise
126 124 log.debug('this %s user exists skipping creation of ldap account',
127 125 username)
128 126 return False
129 127
130 128 def create_registration(self, form_data):
131 129 from rhodecode.lib.celerylib import tasks, run_task
132 130 try:
133 131 new_user = User()
134 132 for k, v in form_data.items():
135 133 if k != 'admin':
136 134 setattr(new_user, k, v)
137 135
138 136 self.sa.add(new_user)
139 137 self.sa.commit()
140 138 body = ('New user registration\n'
141 139 'username: %s\n'
142 140 'email: %s\n')
143 141 body = body % (form_data['username'], form_data['email'])
144 142
145 143 run_task(tasks.send_email, None,
146 144 _('[RhodeCode] New User registration'),
147 145 body)
148 146 except:
149 147 log.error(traceback.format_exc())
150 148 self.sa.rollback()
151 149 raise
152 150
153 151 def update(self, user_id, form_data):
154 152 try:
155 153 user = self.get(user_id, cache=False)
156 154 if user.username == 'default':
157 155 raise DefaultUserException(
158 156 _("You can't Edit this user since it's"
159 157 " crucial for entire application"))
160 158
161 159 for k, v in form_data.items():
162 160 if k == 'new_password' and v != '':
163 161 user.password = v
164 162 user.api_key = generate_api_key(user.username)
165 163 else:
166 164 setattr(user, k, v)
167 165
168 166 self.sa.add(user)
169 167 self.sa.commit()
170 168 except:
171 169 log.error(traceback.format_exc())
172 170 self.sa.rollback()
173 171 raise
174 172
175 173 def update_my_account(self, user_id, form_data):
176 174 try:
177 175 user = self.get(user_id, cache=False)
178 176 if user.username == 'default':
179 177 raise DefaultUserException(
180 178 _("You can't Edit this user since it's"
181 179 " crucial for entire application"))
182 180 for k, v in form_data.items():
183 181 if k == 'new_password' and v != '':
184 182 user.password = v
185 183 user.api_key = generate_api_key(user.username)
186 184 else:
187 185 if k not in ['admin', 'active']:
188 186 setattr(user, k, v)
189 187
190 188 self.sa.add(user)
191 189 self.sa.commit()
192 190 except:
193 191 log.error(traceback.format_exc())
194 192 self.sa.rollback()
195 193 raise
196 194
197 195 def delete(self, user_id):
198 196 try:
199 197 user = self.get(user_id, cache=False)
200 198 if user.username == 'default':
201 199 raise DefaultUserException(
202 200 _("You can't remove this user since it's"
203 201 " crucial for entire application"))
204 202 if user.repositories:
205 203 raise UserOwnsReposException(_('This user still owns %s '
206 204 'repositories and cannot be '
207 205 'removed. Switch owners or '
208 206 'remove those repositories') \
209 207 % user.repositories)
210 208 self.sa.delete(user)
211 209 self.sa.commit()
212 210 except:
213 211 log.error(traceback.format_exc())
214 212 self.sa.rollback()
215 213 raise
216 214
217 215 def reset_password(self, data):
218 216 from rhodecode.lib.celerylib import tasks, run_task
219 217 run_task(tasks.reset_user_password, data['email'])
220 218
221 219
222 220 def fill_data(self, auth_user, user_id=None, api_key=None):
223 221 """
224 222 Fetches auth_user by user_id,or api_key if present.
225 223 Fills auth_user attributes with those taken from database.
226 224 Additionally set's is_authenitated if lookup fails
227 225 present in database
228 226
229 227 :param auth_user: instance of user to set attributes
230 228 :param user_id: user id to fetch by
231 229 :param api_key: api key to fetch by
232 230 """
233 231 if user_id is None and api_key is None:
234 232 raise Exception('You need to pass user_id or api_key')
235 233
236 234 try:
237 235 if api_key:
238 236 dbuser = self.get_by_api_key(api_key)
239 237 else:
240 238 dbuser = self.get(user_id)
241 239
242 240 if dbuser is not None:
243 241 log.debug('filling %s data', dbuser)
244 242 for k, v in dbuser.get_dict().items():
245 243 setattr(auth_user, k, v)
246 244
247 245 except:
248 246 log.error(traceback.format_exc())
249 247 auth_user.is_authenticated = False
250 248
251 249 return auth_user
252 250
253 251
254 252 def fill_perms(self, user):
255 253 """Fills user permission attribute with permissions taken from database
256 254 works for permissions given for repositories, and for permissions that
257 255 as part of beeing group member
258 256
259 257 :param user: user instance to fill his perms
260 258 """
261 259
262 260 user.permissions['repositories'] = {}
263 261 user.permissions['global'] = set()
264 262
265 263 #===========================================================================
266 264 # fetch default permissions
267 265 #===========================================================================
268 266 default_user = self.get_by_username('default', cache=True)
269 267
270 268 default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
271 269 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
272 270 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
273 271 .filter(RepoToPerm.user == default_user).all()
274 272
275 273 if user.is_admin:
276 274 #=======================================================================
277 275 # #admin have all default rights set to admin
278 276 #=======================================================================
279 277 user.permissions['global'].add('hg.admin')
280 278
281 279 for perm in default_perms:
282 280 p = 'repository.admin'
283 281 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
284 282
285 283 else:
286 284 #=======================================================================
287 285 # set default permissions
288 286 #=======================================================================
289 287
290 288 #default global
291 289 default_global_perms = self.sa.query(UserToPerm)\
292 290 .filter(UserToPerm.user == self.sa.query(User)\
293 291 .filter(User.username == 'default').one())
294 292
295 293 for perm in default_global_perms:
296 294 user.permissions['global'].add(perm.permission.permission_name)
297 295
298 296 #default for repositories
299 297 for perm in default_perms:
300 298 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
301 299 #diself.sable defaults for private repos,
302 300 p = 'repository.none'
303 301 elif perm.Repository.user_id == user.user_id:
304 302 #set admin if owner
305 303 p = 'repository.admin'
306 304 else:
307 305 p = perm.Permission.permission_name
308 306
309 307 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
310 308
311 309 #=======================================================================
312 310 # overwrite default with user permissions if any
313 311 #=======================================================================
314 312 user_perms = self.sa.query(RepoToPerm, Permission, Repository)\
315 313 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
316 314 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
317 315 .filter(RepoToPerm.user_id == user.user_id).all()
318 316
319 317 for perm in user_perms:
320 318 if perm.Repository.user_id == user.user_id:#set admin if owner
321 319 p = 'repository.admin'
322 320 else:
323 321 p = perm.Permission.permission_name
324 322 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
325 323
326 324
327 325 #=======================================================================
328 326 # check if user is part of groups for this repository and fill in
329 327 # (or replace with higher) permissions
330 328 #=======================================================================
331 329 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm, Permission, Repository,)\
332 330 .join((Repository, UsersGroupToPerm.repository_id == Repository.repo_id))\
333 331 .join((Permission, UsersGroupToPerm.permission_id == Permission.permission_id))\
334 332 .join((UsersGroupMember, UsersGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
335 333 .filter(UsersGroupMember.user_id == user.user_id).all()
336 334
337 335 for perm in user_perms_from_users_groups:
338 336 p = perm.Permission.permission_name
339 337 cur_perm = user.permissions['repositories'][perm.UsersGroupToPerm.repository.repo_name]
340 338 #overwrite permission only if it's greater than permission given from other sources
341 339 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
342 340 user.permissions['repositories'][perm.UsersGroupToPerm.repository.repo_name] = p
343 341
344 342 return user
@@ -1,111 +1,109 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users groups model for RhodeCode
7 7
8 8 :created_on: Jan 25, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import logging
29 27 import traceback
30 28
31 29 from pylons.i18n.translation import _
32 30
33 31 from rhodecode.model import BaseModel
34 32 from rhodecode.model.caching_query import FromCache
35 33 from rhodecode.model.db import UsersGroup, UsersGroupMember
36 34
37 35 from sqlalchemy.exc import DatabaseError
38 36
39 37 log = logging.getLogger(__name__)
40 38
41 39
42 40 class UsersGroupModel(BaseModel):
43 41
44 42 def get(self, users_group_id, cache=False):
45 43 users_group = self.sa.query(UsersGroup)
46 44 if cache:
47 45 users_group = users_group.options(FromCache("sql_cache_short",
48 46 "get_users_group_%s" % users_group_id))
49 47 return users_group.get(users_group_id)
50 48
51 49
52 50 def get_by_groupname(self, users_group_name, cache=False,
53 51 case_insensitive=False):
54 52
55 53 if case_insensitive:
56 54 user = self.sa.query(UsersGroup)\
57 55 .filter(UsersGroup.users_group_name.ilike(users_group_name))
58 56 else:
59 57 user = self.sa.query(UsersGroup)\
60 58 .filter(UsersGroup.users_group_name == users_group_name)
61 59 if cache:
62 60 user = user.options(FromCache("sql_cache_short",
63 61 "get_user_%s" % users_group_name))
64 62 return user.scalar()
65 63
66 64 def create(self, form_data):
67 65 try:
68 66 new_users_group = UsersGroup()
69 67 for k, v in form_data.items():
70 68 setattr(new_users_group, k, v)
71 69
72 70 self.sa.add(new_users_group)
73 71 self.sa.commit()
74 72 except:
75 73 log.error(traceback.format_exc())
76 74 self.sa.rollback()
77 75 raise
78 76
79 77 def update(self, users_group_id, form_data):
80 78
81 79 try:
82 80 users_group = self.get(users_group_id, cache=False)
83 81
84 82 for k, v in form_data.items():
85 83 if k == 'users_group_members':
86 84 users_group.members = []
87 85 self.sa.flush()
88 86 members_list = []
89 87 if v:
90 88 for u_id in set(v):
91 89 members_list.append(UsersGroupMember(users_group_id,
92 90 u_id))
93 91 setattr(users_group, 'members', members_list)
94 92 setattr(users_group, k, v)
95 93
96 94 self.sa.add(users_group)
97 95 self.sa.commit()
98 96 except:
99 97 log.error(traceback.format_exc())
100 98 self.sa.rollback()
101 99 raise
102 100
103 101 def delete(self, users_group_id):
104 102 try:
105 103 users_group = self.get(users_group_id, cache=False)
106 104 self.sa.delete(users_group)
107 105 self.sa.commit()
108 106 except:
109 107 log.error(traceback.format_exc())
110 108 self.sa.rollback()
111 109 raise
@@ -1,51 +1,49 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.websetup
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Weboperations and setup for rhodecode
7 7
8 8 :created_on: Dec 11, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 25
28 26 import os
29 27 import logging
30 28
31 29 from rhodecode.config.environment import load_environment
32 30 from rhodecode.lib.db_manage import DbManage
33 31
34 32
35 33 log = logging.getLogger(__name__)
36 34
37 35
38 36 def setup_app(command, conf, vars):
39 37 """Place any commands to setup rhodecode here"""
40 38 dbconf = conf['sqlalchemy.db1.url']
41 39 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=conf['here'],
42 40 tests=False)
43 41 dbmanage.create_tables(override=True)
44 42 dbmanage.set_db_version()
45 43 dbmanage.create_settings(dbmanage.config_prompt(None))
46 44 dbmanage.create_default_user()
47 45 dbmanage.admin_prompt()
48 46 dbmanage.create_permissions()
49 47 dbmanage.populate_default_permissions()
50 48
51 49 load_environment(conf.global_conf, conf.local_conf, initial=True)
General Comments 0
You need to be logged in to leave comments. Login now