Show More
@@ -0,0 +1,9 b'' | |||||
|
1 | =================================================================== | |||
|
2 | Cannot display: file marked as a binary type. | |||
|
3 | svn:mime-type = application/octet-stream | |||
|
4 | Index: intl.dll | |||
|
5 | =================================================================== | |||
|
6 | diff --git a/intl.dll b/intl.dll | |||
|
7 | new file mode 10644 | |||
|
8 | --- /dev/null (revision 0) | |||
|
9 | +++ b/intl.dll (revision 1489) |
This diff has been collapsed as it changes many lines, (652 lines changed) Show them Hide them | |||||
@@ -0,0 +1,652 b'' | |||||
|
1 | =================================================================== | |||
|
2 | Cannot display: file marked as a binary type. | |||
|
3 | svn:mime-type = image/png | |||
|
4 | Index: trunk/doc/images/SettingsOverlay.png | |||
|
5 | =================================================================== | |||
|
6 | diff --git a/trunk/doc/images/SettingsOverlay.png b/trunk/doc/images/SettingsOverlay.png | |||
|
7 | GIT binary patch | |||
|
8 | --- a/trunk/doc/images/SettingsOverlay.png (revision 1487) | |||
|
9 | +++ b/trunk/doc/images/SettingsOverlay.png (revision 1488) | |||
|
10 | Index: trunk/doc/source/de/tsvn_ch04.xml | |||
|
11 | =================================================================== | |||
|
12 | diff --git a/trunk/doc/source/de/tsvn_ch04.xml b/trunk/doc/source/de/tsvn_ch04.xml | |||
|
13 | --- a/trunk/doc/source/de/tsvn_ch04.xml (revision 1487) | |||
|
14 | +++ b/trunk/doc/source/de/tsvn_ch04.xml (revision 1488) | |||
|
15 | @@ -1561,39 +1561,49 @@ | |||
|
16 | </figure> | |||
|
17 | Abgesehen von der bevorzugten Sprache erlaubt dieser Dialog es Ihnen, | |||
|
18 | (fast) alle Einstellungen von TortoiseSVN zu ändern. | |||
|
19 | -### Translate ### | |||
|
20 | <variablelist> | |||
|
21 | <varlistentry> | |||
|
22 | - <term>Language</term> | |||
|
23 | - <listitem> | |||
|
24 | - <para>Selects your user interface language. What did you expect?</para> | |||
|
25 | + <term>Sprache</term> | |||
|
26 | + <listitem> | |||
|
27 | + <para>Wählt die Sprache für die Dialoge/Meldungen aus. Was | |||
|
28 | + haben Sie anderes erwartet?</para> | |||
|
29 | </listitem> | |||
|
30 | </varlistentry> | |||
|
31 | ||||
|
32 | <varlistentry> | |||
|
33 | - <term>Exclude pattern</term> | |||
|
34 | + <term>Ausschliessen</term> | |||
|
35 | <listitem> | |||
|
36 | <para> | |||
|
37 | <indexterm> | |||
|
38 | - <primary>exclude pattern</primary> | |||
|
39 | + <primary>ausschliessen</primary> | |||
|
40 | </indexterm> | |||
|
41 | - Exclude files or directories by typing in the names or extensions. Patterns are separated by spaces | |||
|
42 | - e.g. <literal>bin obj *.bak *.~?? *.jar *.[Tt]mp</literal>. The first two entries refer to directories, the | |||
|
43 | - other four to files. | |||
|
44 | - </para> | |||
|
45 | - <para> | |||
|
46 | - This exclude pattern will affect all your projects. It is not versioned, so it | |||
|
47 | - will not affect other users. In contrast you can also use the versioned svn:ignore | |||
|
48 | - property to exclude files or directories from version control. You can set the svn:ignore | |||
|
49 | - property using the | |||
|
50 | + Ausgeschlossene, unversionierte Dateien werden nicht angezeigt | |||
|
51 | + in z.B. dem Ãœbertragen Dialog. Ausserdem werden solche Dateien | |||
|
52 | + beim Importieren in ein Projektarchiv ignoriert. | |||
|
53 | + Schliessen Sie Dateien oder Ordner aus durch Angabe von | |||
|
54 | + Dateinamen oder Erweiterungen. Die einzelnen Muster werden | |||
|
55 | + durch Leerzeichen voneinander getrennt. Zum Beispiel | |||
|
56 | + <literal>bin obj *.bak *.~?? *.jar *.[Tt]mp</literal>. | |||
|
57 | + Die ersten beiden Muster beziehen sich auf Ordner, die | |||
|
58 | + restlichen vier auf Dateien. | |||
|
59 | + </para> | |||
|
60 | + <para> | |||
|
61 | + Diese Auschluss-Muster beziehen sich auf alle Ihre Projekte. | |||
|
62 | + Sie werden nicht versioniert, d.h. andere Benutzer werden davon | |||
|
63 | + nichts mitbekommen. Im Gegensatz dazu können Sie jedoch auch | |||
|
64 | + die versionierte Eigenschaft svn:ignore verwenden, um Dateien | |||
|
65 | + und/oder Ordner von der Versionskontrolle auszuschliessen. | |||
|
66 | + Sie können die svn:ignore Eigenschaft setzen durch den | |||
|
67 | <menuchoice> | |||
|
68 | - <guimenuitem>Add to Ignore List</guimenuitem> | |||
|
69 | + <guimenuitem>Ignorieren</guimenuitem> | |||
|
70 | </menuchoice> | |||
|
71 | - command. After commiting every other user will have the same | |||
|
72 | - svn:ignore property set for this project / directory as you. | |||
|
73 | + Befehl. Nach dem Ãœbertragen wird jeder Benutzer dieselbe | |||
|
74 | + svn:ignore Eigenschaft für das Projekt oder den Ordner | |||
|
75 | + haben wie Sie. | |||
|
76 | </para> | |||
|
77 | </listitem> | |||
|
78 | </varlistentry> | |||
|
79 | +### Translate ### | |||
|
80 | ||||
|
81 | <varlistentry> | |||
|
82 | <term>Default number of log messages</term> | |||
|
83 | @@ -1608,16 +1618,36 @@ | |||
|
84 | </varlistentry> | |||
|
85 | ||||
|
86 | <varlistentry> | |||
|
87 | - <term>Short date / time format in log messages</term> | |||
|
88 | - <listitem> | |||
|
89 | - <para>If the standard long messages use up too much space on your sceen use the short format.</para> | |||
|
90 | + <term>Edit...</term> | |||
|
91 | + <listitem> | |||
|
92 | + <para>... the subversion configuration file directly. Some settings cannot be modified by TortoiseSVN.</para> | |||
|
93 | </listitem> | |||
|
94 | </varlistentry> | |||
|
95 | ||||
|
96 | <varlistentry> | |||
|
97 | - <term>Edit...</term> | |||
|
98 | - <listitem> | |||
|
99 | - <para>... the subversion configuration file directly. Some settings cannot be modified by TortoiseSVN.</para> | |||
|
100 | + <term>Short date / time format in log messages</term> | |||
|
101 | + <listitem> | |||
|
102 | + <para>If the standard long messages use up too much space on your sceen use the short format.</para> | |||
|
103 | + </listitem> | |||
|
104 | + </varlistentry> | |||
|
105 | + | |||
|
106 | + <varlistentry> | |||
|
107 | + <term>Set filedates to "last commit time"</term> | |||
|
108 | + <listitem> | |||
|
109 | + <para> | |||
|
110 | + This option tells TortoiseSVN to set the filedates to the last commit time | |||
|
111 | + when doing a checkout or an update. Otherwise TortoiseSVN will use | |||
|
112 | + the current date. | |||
|
113 | + </para> | |||
|
114 | + </listitem> | |||
|
115 | + </varlistentry> | |||
|
116 | + | |||
|
117 | + <varlistentry> | |||
|
118 | + <term>Close windows automatically</term> | |||
|
119 | + <listitem> | |||
|
120 | + <para> | |||
|
121 | + TortoiseSVN will automatically close all progress dialogs when the action is finished. | |||
|
122 | + </para> | |||
|
123 | </listitem> | |||
|
124 | </varlistentry> | |||
|
125 | ||||
|
126 | @@ -1629,15 +1659,15 @@ | |||
|
127 | </varlistentry> | |||
|
128 | ||||
|
129 | <varlistentry> | |||
|
130 | - <term>Set filedates to "last commit time"</term> | |||
|
131 | - <listitem> | |||
|
132 | - <para> | |||
|
133 | - This option tells TortoiseSVN to set the filedates to the last commit time | |||
|
134 | - when doing a checkout or an update. Otherwise TortoiseSVN will use | |||
|
135 | - the current date. | |||
|
136 | + <term>Minimum logsize in chars</term> | |||
|
137 | + <listitem> | |||
|
138 | + <para> | |||
|
139 | + The minimum length of a log message for a commit. If you enter | |||
|
140 | + a shorter message than specified here, the commit is disabled. | |||
|
141 | </para> | |||
|
142 | </listitem> | |||
|
143 | </varlistentry> | |||
|
144 | + | |||
|
145 | <varlistentry> | |||
|
146 | <term>Don't remove log messages when cancelling a commit</term> | |||
|
147 | <listitem> | |||
|
148 | @@ -1648,11 +1678,14 @@ | |||
|
149 | </para> | |||
|
150 | </listitem> | |||
|
151 | </varlistentry> | |||
|
152 | + | |||
|
153 | <varlistentry> | |||
|
154 | - <term>Close windows automatically</term> | |||
|
155 | - <listitem> | |||
|
156 | - <para> | |||
|
157 | - TortoiseSVN will automatically close all progress dialogs when the action is finished. | |||
|
158 | + <term>Show BugID/Issue-Nr. Box</term> | |||
|
159 | + <listitem> | |||
|
160 | + <para> | |||
|
161 | + Shows a textbox in the commit dialog where you can enter | |||
|
162 | + a BugID or Issue-Nr. from a bugtracker to associate the | |||
|
163 | + commit with that ID/number. | |||
|
164 | </para> | |||
|
165 | </listitem> | |||
|
166 | </varlistentry> | |||
|
167 | @@ -1673,10 +1706,32 @@ | |||
|
168 | Sie können auch alle überlagerten Icons deaktivieren, aber wo liegt der Spaß darin? | |||
|
169 | </para> | |||
|
170 | <para> | |||
|
171 | + Die <term>Ausschluss Pfade</term> sagen TortoiseSVN für welche | |||
|
172 | + Pfade die überlagerten Icons <emphasis>nicht</emphasis> gezeichnet | |||
|
173 | + werden sollen. Dies ist nützlich wenn Sie zum Beispiel sehr grosse | |||
|
174 | + Arbeitskopien haben, welche grosse externe Bibliotheken, welche Sie | |||
|
175 | + selbst nie ändern werden enthalten. Sie können dann diese Pfade | |||
|
176 | + ausschliessen. Zum Beispiel: | |||
|
177 | + </para> | |||
|
178 | + <para> | |||
|
179 | + <filename>f:\development\SVN\Subversion</filename> deaktiviert | |||
|
180 | + die überlagerten Icons <emphasis>nur</emphasis> für diesen speziellen | |||
|
181 | + Ordner. Sie können die Icons noch immer für alle Dateien und Ordner | |||
|
182 | + innerhalb sehen. | |||
|
183 | + </para> | |||
|
184 | + <para> | |||
|
185 | + <filename>f:\development\SVN\Subversion*</filename> deaktiviert die | |||
|
186 | + überlagerten Icons für <emphasis>alle</emphasis> Dateien und Ordner | |||
|
187 | + welcher Pfad mit <filename>f:\development\SVN\Subversion</filename> | |||
|
188 | + beginnt. Das bedeutet dass auch für alle Dateien und Ordner innerhalb | |||
|
189 | + keine überlagerten Icons angezeigt werden. | |||
|
190 | + </para> | |||
|
191 | + <para> | |||
|
192 | Ausserdem können Sie angeben, welche Befehle im | |||
|
193 | Hauptkontextmenu des Explorer angezeigt werden sollen und welche | |||
|
194 | Sie lieber im Untermenu haben wollen. | |||
|
195 | </para> | |||
|
196 | + </sect2> | |||
|
197 | <sect2 id="tsvn-DUG-settings-network"> | |||
|
198 | <?dbhh topicname="HIDD_SETTINGSPROXY"?> | |||
|
199 | <title>Der Einstellungsdialog, Netzwerkseite</title> | |||
|
200 | Index: trunk/doc/source/en/tsvn_ch04.xml | |||
|
201 | =================================================================== | |||
|
202 | diff --git a/trunk/doc/source/en/tsvn_ch04.xml b/trunk/doc/source/en/tsvn_ch04.xml | |||
|
203 | --- a/trunk/doc/source/en/tsvn_ch04.xml (revision 1487) | |||
|
204 | +++ b/trunk/doc/source/en/tsvn_ch04.xml (revision 1488) | |||
|
205 | @@ -1457,7 +1457,7 @@ | |||
|
206 | <varlistentry> | |||
|
207 | <term>Language</term> | |||
|
208 | <listitem> | |||
|
209 | - <para>Selects your user interface language. What did you expect?</para> | |||
|
210 | + <para>Selects your user interface language. What else did you expect?</para> | |||
|
211 | </listitem> | |||
|
212 | </varlistentry> | |||
|
213 | ||||
|
214 | @@ -1468,6 +1468,9 @@ | |||
|
215 | <indexterm> | |||
|
216 | <primary>exclude pattern</primary> | |||
|
217 | </indexterm> | |||
|
218 | + Exclude patterns are used to prevent unversioned files from | |||
|
219 | + showing up e.g. in the commit dialog. Files matching the | |||
|
220 | + patterns are also ignored by an import. | |||
|
221 | Exclude files or directories by typing in the names or extensions. Patterns are separated by spaces | |||
|
222 | e.g. <literal>bin obj *.bak *.~?? *.jar *.[Tt]mp</literal>. The first two entries refer to directories, the | |||
|
223 | other four to files. | |||
|
224 | @@ -1499,23 +1502,16 @@ | |||
|
225 | </varlistentry> | |||
|
226 | ||||
|
227 | <varlistentry> | |||
|
228 | + <term>Edit...</term> | |||
|
229 | + <listitem> | |||
|
230 | + <para>... the subversion configuration file directly. Some settings cannot be modified by TortoiseSVN.</para> | |||
|
231 | + </listitem> | |||
|
232 | + </varlistentry> | |||
|
233 | + | |||
|
234 | + <varlistentry> | |||
|
235 | <term>Short date / time format in log messages</term> | |||
|
236 | <listitem> | |||
|
237 | <para>If the standard long messages use up too much space on your sceen use the short format.</para> | |||
|
238 | - </listitem> | |||
|
239 | - </varlistentry> | |||
|
240 | - | |||
|
241 | - <varlistentry> | |||
|
242 | - <term>Edit...</term> | |||
|
243 | - <listitem> | |||
|
244 | - <para>... the subversion configuration file directly. Some settings cannot be modified by TortoiseSVN.</para> | |||
|
245 | - </listitem> | |||
|
246 | - </varlistentry> | |||
|
247 | - | |||
|
248 | - <varlistentry> | |||
|
249 | - <term>Check for newer versions</term> | |||
|
250 | - <listitem> | |||
|
251 | - <para>If checked, TortoiseSVN will check once a week if an update is available</para> | |||
|
252 | </listitem> | |||
|
253 | </varlistentry> | |||
|
254 | ||||
|
255 | @@ -1529,6 +1525,33 @@ | |||
|
256 | </para> | |||
|
257 | </listitem> | |||
|
258 | </varlistentry> | |||
|
259 | + | |||
|
260 | + <varlistentry> | |||
|
261 | + <term>Close windows automatically</term> | |||
|
262 | + <listitem> | |||
|
263 | + <para> | |||
|
264 | + TortoiseSVN will automatically close all progress dialogs when the action is finished. | |||
|
265 | + </para> | |||
|
266 | + </listitem> | |||
|
267 | + </varlistentry> | |||
|
268 | + | |||
|
269 | + <varlistentry> | |||
|
270 | + <term>Check for newer versions</term> | |||
|
271 | + <listitem> | |||
|
272 | + <para>If checked, TortoiseSVN will check once a week if an update is available</para> | |||
|
273 | + </listitem> | |||
|
274 | + </varlistentry> | |||
|
275 | + | |||
|
276 | + <varlistentry> | |||
|
277 | + <term>Minimum logsize in chars</term> | |||
|
278 | + <listitem> | |||
|
279 | + <para> | |||
|
280 | + The minimum length of a log message for a commit. If you enter | |||
|
281 | + a shorter message than specified here, the commit is disabled. | |||
|
282 | + </para> | |||
|
283 | + </listitem> | |||
|
284 | + </varlistentry> | |||
|
285 | + | |||
|
286 | <varlistentry> | |||
|
287 | <term>Don't remove log messages when cancelling a commit</term> | |||
|
288 | <listitem> | |||
|
289 | @@ -1539,11 +1562,14 @@ | |||
|
290 | </para> | |||
|
291 | </listitem> | |||
|
292 | </varlistentry> | |||
|
293 | + | |||
|
294 | <varlistentry> | |||
|
295 | - <term>Close windows automatically</term> | |||
|
296 | - <listitem> | |||
|
297 | - <para> | |||
|
298 | - TortoiseSVN will automatically close all progress dialogs when the action is finished. | |||
|
299 | + <term>Show BugID/Issue-Nr. Box</term> | |||
|
300 | + <listitem> | |||
|
301 | + <para> | |||
|
302 | + Shows a textbox in the commit dialog where you can enter | |||
|
303 | + a BugID or Issue-Nr. from a bugtracker to associate the | |||
|
304 | + commit with that ID/number. | |||
|
305 | </para> | |||
|
306 | </listitem> | |||
|
307 | </varlistentry> | |||
|
308 | @@ -1552,7 +1578,7 @@ | |||
|
309 | </sect2> | |||
|
310 | <sect2 id="tsvn-DUG-settings-overlay"> | |||
|
311 | <?dbhh topicname="HIDD_SETTINGSOVERLAY"?> | |||
|
312 | - <title>The Settings Dialog, Overlay Tab</title> | |||
|
313 | + <title>The Settings Dialog, Look and Feel Tab</title> | |||
|
314 | <para> | |||
|
315 | <figure id="tsvn-DUG-settings-dia-2"> | |||
|
316 | <title>The Settings Dialog, Overlay Tab</title> | |||
|
317 | @@ -1560,8 +1586,27 @@ | |||
|
318 | </figure> | |||
|
319 | This tab allows you to choose, for which items TortoiseSVN shall | |||
|
320 | display icon overlays. If you feel that your icon overlays are very | |||
|
321 | - slow (explore is not responsive), uncheck the "show changed directories" box. | |||
|
322 | + slow (explorer is not responsive), uncheck the "show changed directories" box. | |||
|
323 | You can even disable all icon overlays, but where's the fun in that? | |||
|
324 | + </para> | |||
|
325 | + <para> | |||
|
326 | + The <term>Exclude Paths</term> are used to tell TortoiseSVN for which | |||
|
327 | + paths <emphasis>not</emphasis> to show icon overlays and status columns. | |||
|
328 | + This is useful if you have some very big working copies containing | |||
|
329 | + only libraries which you won't change at all and therefore don't | |||
|
330 | + need the overlays. For example: | |||
|
331 | + </para> | |||
|
332 | + <para> | |||
|
333 | + <filename>f:\development\SVN\Subversion</filename> will disable | |||
|
334 | + the overlays on <emphasis>only</emphasis> that specific folder. You | |||
|
335 | + still can see the overlays on all files and folder inside that folder. | |||
|
336 | + </para> | |||
|
337 | + <para> | |||
|
338 | + <filename>f:\development\SVN\Subversion*</filename> will disable the | |||
|
339 | + overlays on <emphasis>all</emphasis> files and folders which path | |||
|
340 | + starts with <filename>f:\development\SVN\Subversion</filename>. That | |||
|
341 | + means you won't see overlays for all files and folder below that | |||
|
342 | + path. | |||
|
343 | </para> | |||
|
344 | <para> | |||
|
345 | You can also specifiy here which of the TortoiseSVN contex menu | |||
|
346 | Index: trunk/src/Changelog.txt | |||
|
347 | =================================================================== | |||
|
348 | diff --git a/trunk/src/Changelog.txt b/trunk/src/Changelog.txt | |||
|
349 | --- a/trunk/src/Changelog.txt (revision 1487) | |||
|
350 | +++ b/trunk/src/Changelog.txt (revision 1488) | |||
|
351 | @@ -1,3 +1,5 @@ | |||
|
352 | +- ADD: Option to exclude specific paths from showing | |||
|
353 | + icon overlays. (Stefan) | |||
|
354 | - ADD: On Win2k and later, the authentication data is now | |||
|
355 | encrypted before saved. The encryption is not available | |||
|
356 | for the other OS's. (Stefan) | |||
|
357 | Index: trunk/src/Resources/TortoiseProcENG.rc | |||
|
358 | =================================================================== | |||
|
359 | diff --git a/trunk/src/Resources/TortoiseProcENG.rc b/trunk/src/Resources/TortoiseProcENG.rc | |||
|
360 | --- a/trunk/src/Resources/TortoiseProcENG.rc (revision 1487) | |||
|
361 | +++ b/trunk/src/Resources/TortoiseProcENG.rc (revision 1488) | |||
|
362 | @@ -398,27 +398,31 @@ | |||
|
363 | BEGIN | |||
|
364 | CONTROL "&Indicate folders with changed contents", | |||
|
365 | IDC_CHANGEDDIRS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12, | |||
|
366 | - 20,145,10 | |||
|
367 | + 20,206,10 | |||
|
368 | CONTROL "&Removable drives",IDC_REMOVABLE,"Button", | |||
|
369 | - BS_AUTOCHECKBOX | WS_TABSTOP,18,66,130,10 | |||
|
370 | + BS_AUTOCHECKBOX | WS_TABSTOP,18,58,130,10 | |||
|
371 | CONTROL "&Network drives",IDC_NETWORK,"Button",BS_AUTOCHECKBOX | | |||
|
372 | - WS_TABSTOP,18,76,130,10 | |||
|
373 | + WS_TABSTOP,18,68,130,10 | |||
|
374 | CONTROL "&Fixed drives",IDC_FIXED,"Button",BS_AUTOCHECKBOX | | |||
|
375 | - WS_TABSTOP,18,87,127,10 | |||
|
376 | + WS_TABSTOP,18,79,127,10 | |||
|
377 | CONTROL "&CD-ROM",IDC_CDROM,"Button",BS_AUTOCHECKBOX | | |||
|
378 | - WS_TABSTOP,159,66,118,10 | |||
|
379 | - GROUPBOX "Drive Types",IDC_DRIVEGROUP,12,52,274,50 | |||
|
380 | + WS_TABSTOP,166,58,118,10 | |||
|
381 | + GROUPBOX "Drive Types",IDC_DRIVEGROUP,12,44,274,50 | |||
|
382 | CONTROL "RAM drives",IDC_RAM,"Button",BS_AUTOCHECKBOX | | |||
|
383 | - WS_TABSTOP,159,76,119,10 | |||
|
384 | + WS_TABSTOP,166,68,119,10 | |||
|
385 | CONTROL "Unknown drives",IDC_UNKNOWN,"Button",BS_AUTOCHECKBOX | | |||
|
386 | - WS_TABSTOP,159,86,118,10 | |||
|
387 | + WS_TABSTOP,166,78,118,10 | |||
|
388 | CONTROL "Show overlays only in explorer",IDC_ONLYEXPLORER,"Button", | |||
|
389 | - BS_AUTOCHECKBOX | WS_TABSTOP,12,33,122,10 | |||
|
390 | - GROUPBOX "Icon Overlays / Status Columns",IDC_STATIC,7,7,286,103 | |||
|
391 | - GROUPBOX "Context Menu",IDC_STATIC,7,113,286,97 | |||
|
392 | + BS_AUTOCHECKBOX | WS_TABSTOP,12,33,190,10 | |||
|
393 | + GROUPBOX "Icon Overlays / Status Columns",IDC_STATIC,7,7,286,118 | |||
|
394 | + GROUPBOX "Context Menu",IDC_STATIC,7,130,286,80 | |||
|
395 | CONTROL "",IDC_MENULIST,"SysListView32",LVS_REPORT | | |||
|
396 | LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | | |||
|
397 | - WS_BORDER | WS_TABSTOP,12,125,274,78 | |||
|
398 | + WS_BORDER | WS_TABSTOP,12,140,274,63 | |||
|
399 | + LTEXT "Exclude paths:",IDC_STATIC,12,106,85,8 | |||
|
400 | + EDITTEXT IDC_EXCLUDEPATHS,102,96,184,25,ES_MULTILINE | | |||
|
401 | + ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | | |||
|
402 | + WS_VSCROLL | |||
|
403 | END | |||
|
404 | ||||
|
405 | IDD_SETTINGSPROXY DIALOGEX 0, 0, 300, 217 | |||
|
406 | @@ -860,7 +864,7 @@ | |||
|
407 | RIGHTMARGIN, 293 | |||
|
408 | VERTGUIDE, 12 | |||
|
409 | VERTGUIDE, 18 | |||
|
410 | - VERTGUIDE, 159 | |||
|
411 | + VERTGUIDE, 166 | |||
|
412 | VERTGUIDE, 286 | |||
|
413 | TOPMARGIN, 7 | |||
|
414 | BOTTOMMARGIN, 210 | |||
|
415 | @@ -1377,6 +1381,8 @@ | |||
|
416 | "If activated, prevents the overlays from showing in ""save as.."" or ""open"" dialogs" | |||
|
417 | IDS_SETTINGS_MENULAYOUT_TT | |||
|
418 | "Check those menu entries you want to appear in the top context menu instead of the submenu" | |||
|
419 | + IDS_SETTINGS_EXCLUDELIST_TT | |||
|
420 | + "A newline separated list of paths for which no icon overlays are shown.\nIf you add an ""*"" char at the end of a path, then all files and subdirs inside that path are excluded too.\nAn empty list will allow overlays on all paths." | |||
|
421 | END | |||
|
422 | ||||
|
423 | STRINGTABLE | |||
|
424 | Index: trunk/src/TortoiseProc/SetOverlayPage.cpp | |||
|
425 | =================================================================== | |||
|
426 | diff --git a/trunk/src/TortoiseProc/SetOverlayPage.cpp b/trunk/src/TortoiseProc/SetOverlayPage.cpp | |||
|
427 | --- a/trunk/src/TortoiseProc/SetOverlayPage.cpp (revision 1487) | |||
|
428 | +++ b/trunk/src/TortoiseProc/SetOverlayPage.cpp (revision 1488) | |||
|
429 | @@ -20,6 +20,7 @@ | |||
|
430 | #include "TortoiseProc.h" | |||
|
431 | #include "SetOverlayPage.h" | |||
|
432 | #include "Globals.h" | |||
|
433 | +#include ".\setoverlaypage.h" | |||
|
434 | ||||
|
435 | ||||
|
436 | // CSetOverlayPage dialog | |||
|
437 | @@ -35,6 +36,7 @@ | |||
|
438 | , m_bRAM(FALSE) | |||
|
439 | , m_bUnknown(FALSE) | |||
|
440 | , m_bOnlyExplorer(FALSE) | |||
|
441 | + , m_sExcludePaths(_T("")) | |||
|
442 | { | |||
|
443 | m_regShowChangedDirs = CRegDWORD(_T("Software\\TortoiseSVN\\RecursiveOverlay")); | |||
|
444 | m_regOnlyExplorer = CRegDWORD(_T("Software\\TortoiseSVN\\OverlaysOnlyInExplorer"), FALSE); | |||
|
445 | @@ -45,6 +47,7 @@ | |||
|
446 | m_regDriveMaskRAM = CRegDWORD(_T("Software\\TortoiseSVN\\DriveMaskRAM")); | |||
|
447 | m_regDriveMaskUnknown = CRegDWORD(_T("Software\\TortoiseSVN\\DriveMaskUnknown")); | |||
|
448 | m_regTopmenu = CRegDWORD(_T("Software\\TortoiseSVN\\ContextMenuEntries"), MENUCHECKOUT | MENUUPDATE | MENUCOMMIT); | |||
|
449 | + m_regExcludePaths = CRegString(_T("Software\\TortoiseSVN\\OverlayExcludeList")); | |||
|
450 | ||||
|
451 | m_bShowChangedDirs = m_regShowChangedDirs; | |||
|
452 | m_bOnlyExplorer = m_regOnlyExplorer; | |||
|
453 | @@ -55,6 +58,8 @@ | |||
|
454 | m_bRAM = m_regDriveMaskRAM; | |||
|
455 | m_bUnknown = m_regDriveMaskUnknown; | |||
|
456 | m_topmenu = m_regTopmenu; | |||
|
457 | + m_sExcludePaths = m_regExcludePaths; | |||
|
458 | + m_sExcludePaths.Replace(_T("\n"), _T("\r\n")); | |||
|
459 | } | |||
|
460 | ||||
|
461 | CSetOverlayPage::~CSetOverlayPage() | |||
|
462 | @@ -74,6 +79,7 @@ | |||
|
463 | DDX_Control(pDX, IDC_DRIVEGROUP, m_cDriveGroup); | |||
|
464 | DDX_Check(pDX, IDC_ONLYEXPLORER, m_bOnlyExplorer); | |||
|
465 | DDX_Control(pDX, IDC_MENULIST, m_cMenuList); | |||
|
466 | + DDX_Text(pDX, IDC_EXCLUDEPATHS, m_sExcludePaths); | |||
|
467 | } | |||
|
468 | ||||
|
469 | ||||
|
470 | @@ -87,6 +93,7 @@ | |||
|
471 | ON_BN_CLICKED(IDC_RAM, OnBnClickedRam) | |||
|
472 | ON_BN_CLICKED(IDC_ONLYEXPLORER, OnBnClickedOnlyexplorer) | |||
|
473 | ON_NOTIFY(LVN_ITEMCHANGED, IDC_MENULIST, OnLvnItemchangedMenulist) | |||
|
474 | + ON_EN_CHANGE(IDC_EXCLUDEPATHS, OnEnChangeExcludepaths) | |||
|
475 | END_MESSAGE_MAP() | |||
|
476 | ||||
|
477 | ||||
|
478 | @@ -103,6 +110,9 @@ | |||
|
479 | m_regDriveMaskRAM = m_bRAM; | |||
|
480 | m_regDriveMaskUnknown = m_bUnknown; | |||
|
481 | m_regTopmenu = m_topmenu; | |||
|
482 | + m_sExcludePaths.Replace(_T("\r"), _T("")); | |||
|
483 | + m_regExcludePaths = m_sExcludePaths; | |||
|
484 | + m_sExcludePaths.Replace(_T("\n"), _T("\r\n")); | |||
|
485 | } | |||
|
486 | } | |||
|
487 | ||||
|
488 | @@ -116,7 +126,7 @@ | |||
|
489 | m_tooltips.AddTool(IDC_CHANGEDDIRS, IDS_SETTINGS_CHANGEDDIRS_TT); | |||
|
490 | m_tooltips.AddTool(IDC_ONLYEXPLORER, IDS_SETTINGS_ONLYEXPLORER_TT); | |||
|
491 | m_tooltips.AddTool(IDC_MENULIST, IDS_SETTINGS_MENULAYOUT_TT); | |||
|
492 | - | |||
|
493 | + m_tooltips.AddTool(IDC_EXCLUDEPATHS, IDS_SETTINGS_EXCLUDELIST_TT); | |||
|
494 | ||||
|
495 | m_cMenuList.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER); | |||
|
496 | ||||
|
497 | @@ -280,3 +290,8 @@ | |||
|
498 | } // if (m_cMenuList.GetItemCount() > 0) | |||
|
499 | *pResult = 0; | |||
|
500 | } | |||
|
501 | + | |||
|
502 | +void CSetOverlayPage::OnEnChangeExcludepaths() | |||
|
503 | +{ | |||
|
504 | + SetModified(); | |||
|
505 | +} | |||
|
506 | Index: trunk/src/TortoiseProc/SetOverlayPage.h | |||
|
507 | =================================================================== | |||
|
508 | diff --git a/trunk/src/TortoiseProc/SetOverlayPage.h b/trunk/src/TortoiseProc/SetOverlayPage.h | |||
|
509 | --- a/trunk/src/TortoiseProc/SetOverlayPage.h (revision 1487) | |||
|
510 | +++ b/trunk/src/TortoiseProc/SetOverlayPage.h (revision 1488) | |||
|
511 | @@ -92,6 +92,8 @@ | |||
|
512 | CIconStatic m_cDriveGroup; | |||
|
513 | BOOL m_bInitialized; | |||
|
514 | CRegDWORD m_regTopmenu; | |||
|
515 | + CRegString m_regExcludePaths; | |||
|
516 | + CString m_sExcludePaths; | |||
|
517 | ||||
|
518 | CImageList m_imgList; | |||
|
519 | CListCtrl m_cMenuList; | |||
|
520 | @@ -110,4 +112,5 @@ | |||
|
521 | virtual BOOL OnApply(); | |||
|
522 | afx_msg void OnBnClickedOnlyexplorer(); | |||
|
523 | afx_msg void OnLvnItemchangedMenulist(NMHDR *pNMHDR, LRESULT *pResult); | |||
|
524 | + afx_msg void OnEnChangeExcludepaths(); | |||
|
525 | }; | |||
|
526 | Index: trunk/src/TortoiseProc/resource.h | |||
|
527 | =================================================================== | |||
|
528 | diff --git a/trunk/src/TortoiseProc/resource.h b/trunk/src/TortoiseProc/resource.h | |||
|
529 | --- a/trunk/src/TortoiseProc/resource.h (revision 1487) | |||
|
530 | +++ b/trunk/src/TortoiseProc/resource.h (revision 1488) | |||
|
531 | @@ -179,6 +179,7 @@ | |||
|
532 | #define IDC_MINLOGSIZE 1077 | |||
|
533 | #define IDC_BUGID 1077 | |||
|
534 | #define IDC_WCURL 1077 | |||
|
535 | +#define IDC_EXCLUDEPATHS 1077 | |||
|
536 | #define IDC_DRIVEGROUP 1079 | |||
|
537 | #define IDC_PROXYGROUP 1080 | |||
|
538 | #define IDC_SSHGROUP 1081 | |||
|
539 | @@ -427,6 +428,7 @@ | |||
|
540 | #define IDS_SETTINGS_CHECKNEWER_TT 3100 | |||
|
541 | #define IDS_SETTINGS_ONLYEXPLORER_TT 3101 | |||
|
542 | #define IDS_SETTINGS_MENULAYOUT_TT 3102 | |||
|
543 | +#define IDS_SETTINGS_EXCLUDELIST_TT 3103 | |||
|
544 | #define IDS_CHECKNEWER_YOURVERSION 3200 | |||
|
545 | #define IDS_CHECKNEWER_CURRENTVERSION 3201 | |||
|
546 | #define IDS_CHECKNEWER_YOURUPTODATE 3202 | |||
|
547 | Index: trunk/src/TortoiseShell/ShellCache.h | |||
|
548 | =================================================================== | |||
|
549 | diff --git a/trunk/src/TortoiseShell/ShellCache.h b/trunk/src/TortoiseShell/ShellCache.h | |||
|
550 | --- a/trunk/src/TortoiseShell/ShellCache.h (revision 1487) | |||
|
551 | +++ b/trunk/src/TortoiseShell/ShellCache.h (revision 1488) | |||
|
552 | @@ -21,9 +21,11 @@ | |||
|
553 | #include "globals.h" | |||
|
554 | #include <tchar.h> | |||
|
555 | #include <string> | |||
|
556 | +#include <vector> | |||
|
557 | #include "registry.h" | |||
|
558 | ||||
|
559 | #define REGISTRYTIMEOUT 2000 | |||
|
560 | +#define EXCLUDELISTTIMEOUT 5000 | |||
|
561 | #define DRIVETYPETIMEOUT 300000 // 5 min | |||
|
562 | #define NUMBERFMTTIMEOUT 300000 | |||
|
563 | class ShellCache | |||
|
564 | @@ -39,12 +41,14 @@ | |||
|
565 | driveremove = CRegStdWORD(_T("Software\\TortoiseSVN\\DriveMaskRemovable")); | |||
|
566 | driveram = CRegStdWORD(_T("Software\\TortoiseSVN\\DriveMaskRAM")); | |||
|
567 | driveunknown = CRegStdWORD(_T("Software\\TortoiseSVN\\DriveMaskUnknown")); | |||
|
568 | + excludelist = CRegStdString(_T("Software\\TortoiseSVN\\OverlayExcludeList")); | |||
|
569 | recursiveticker = GetTickCount(); | |||
|
570 | folderoverlayticker = GetTickCount(); | |||
|
571 | driveticker = recursiveticker; | |||
|
572 | drivetypeticker = recursiveticker; | |||
|
573 | langticker = recursiveticker; | |||
|
574 | - columnrevformatticker = langticker; | |||
|
575 | + columnrevformatticker = recursiveticker; | |||
|
576 | + excludelistticker = recursiveticker; | |||
|
577 | menulayout = CRegStdWORD(_T("Software\\TortoiseSVN\\ContextMenuEntries"), MENUCHECKOUT | MENUUPDATE | MENUCOMMIT); | |||
|
578 | langid = CRegStdWORD(_T("Software\\TortoiseSVN\\LanguageID"), 1033); | |||
|
579 | blockstatus = CRegStdWORD(_T("Software\\TortoiseSVN\\BlockStatus"), 0); | |||
|
580 | @@ -177,6 +181,21 @@ | |||
|
581 | return FALSE; | |||
|
582 | if ((drivetype == DRIVE_UNKNOWN)&&(IsUnknown())) | |||
|
583 | return FALSE; | |||
|
584 | + | |||
|
585 | + ExcludeListValid(); | |||
|
586 | + for (std::vector<stdstring>::iterator I = exvector.begin(); I != exvector.end(); ++I) | |||
|
587 | + { | |||
|
588 | + if (I->empty()) | |||
|
589 | + continue; | |||
|
590 | + if (I->at(I->size()-1)=='*') | |||
|
591 | + { | |||
|
592 | + stdstring str = I->substr(0, I->size()-1); | |||
|
593 | + if (_tcsnicmp(str.c_str(), path, str.size())==0) | |||
|
594 | + return FALSE; | |||
|
595 | + } | |||
|
596 | + else if (_tcsicmp(I->c_str(), path)==0) | |||
|
597 | + return FALSE; | |||
|
598 | + } | |||
|
599 | return TRUE; | |||
|
600 | } | |||
|
601 | DWORD GetLangID() | |||
|
602 | @@ -218,6 +237,32 @@ | |||
|
603 | driveremove.read(); | |||
|
604 | } | |||
|
605 | } | |||
|
606 | + void ExcludeListValid() | |||
|
607 | + { | |||
|
608 | + if ((GetTickCount() - EXCLUDELISTTIMEOUT)>excludelistticker) | |||
|
609 | + { | |||
|
610 | + excludelistticker = GetTickCount(); | |||
|
611 | + excludelist.read(); | |||
|
612 | + if (excludeliststr.compare((stdstring)excludelist)==0) | |||
|
613 | + return; | |||
|
614 | + excludeliststr = (stdstring)excludelist; | |||
|
615 | + exvector.clear(); | |||
|
616 | + int pos = 0, pos_ant = 0; | |||
|
617 | + pos = excludeliststr.find(_T("\n"), pos_ant); | |||
|
618 | + while (pos != stdstring::npos) | |||
|
619 | + { | |||
|
620 | + stdstring token = excludeliststr.substr(pos_ant, pos-pos_ant); | |||
|
621 | + exvector.push_back(token); | |||
|
622 | + pos_ant = pos+1; | |||
|
623 | + pos = excludeliststr.find(_T("\n"), pos_ant); | |||
|
624 | + } | |||
|
625 | + if (!excludeliststr.empty()) | |||
|
626 | + { | |||
|
627 | + exvector.push_back(excludeliststr.substr(pos_ant, excludeliststr.size()-1)); | |||
|
628 | + } | |||
|
629 | + excludeliststr = (stdstring)excludelist; | |||
|
630 | + } | |||
|
631 | + } | |||
|
632 | CRegStdWORD blockstatus; | |||
|
633 | CRegStdWORD langid; | |||
|
634 | CRegStdWORD showrecursive; | |||
|
635 | @@ -229,6 +274,9 @@ | |||
|
636 | CRegStdWORD driveram; | |||
|
637 | CRegStdWORD driveunknown; | |||
|
638 | CRegStdWORD menulayout; | |||
|
639 | + CRegStdString excludelist; | |||
|
640 | + stdstring excludeliststr; | |||
|
641 | + std::vector<stdstring> exvector; | |||
|
642 | DWORD recursiveticker; | |||
|
643 | DWORD folderoverlayticker; | |||
|
644 | DWORD driveticker; | |||
|
645 | @@ -237,6 +285,7 @@ | |||
|
646 | DWORD langticker; | |||
|
647 | DWORD blockstatusticker; | |||
|
648 | DWORD columnrevformatticker; | |||
|
649 | + DWORD excludelistticker; | |||
|
650 | UINT drivetypecache[27]; | |||
|
651 | TCHAR drivetypepathcache[MAX_PATH]; | |||
|
652 | NUMBERFMT columnrevformat; No newline at end of file |
@@ -0,0 +1,192 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2010-2016 RhodeCode GmbH | |||
|
4 | # | |||
|
5 | # This program is free software: you can redistribute it and/or modify | |||
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU Affero General Public License | |||
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | ||||
|
21 | import os | |||
|
22 | ||||
|
23 | import mock | |||
|
24 | import pytest | |||
|
25 | ||||
|
26 | from rhodecode.controllers.files import FilesController | |||
|
27 | from rhodecode.lib import helpers as h | |||
|
28 | from rhodecode.lib.compat import OrderedDict | |||
|
29 | from rhodecode.lib.ext_json import json | |||
|
30 | from rhodecode.lib.vcs import nodes | |||
|
31 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |||
|
32 | from rhodecode.lib.vcs.conf import settings | |||
|
33 | from rhodecode.lib.vcs.nodes import FileNode | |||
|
34 | from rhodecode.model.db import Repository | |||
|
35 | from rhodecode.model.scm import ScmModel | |||
|
36 | from rhodecode.tests import ( | |||
|
37 | url, TEST_USER_ADMIN_LOGIN, assert_session_flash, assert_not_in_session_flash) | |||
|
38 | from rhodecode.tests.fixture import Fixture | |||
|
39 | from rhodecode.tests.utils import commit_change | |||
|
40 | ||||
|
41 | fixture = Fixture() | |||
|
42 | ||||
|
43 | ||||
|
44 | @pytest.mark.usefixtures("autologin_user", "app") | |||
|
45 | class TestSideBySideDiff(object): | |||
|
46 | ||||
|
47 | def test_diff_side_by_side(self, app, backend, backend_stub): | |||
|
48 | f_path = 'test_sidebyside_file.py' | |||
|
49 | commit1_content = 'content-25d7e49c18b159446c\n' | |||
|
50 | commit2_content = 'content-603d6c72c46d953420\n' | |||
|
51 | repo = backend.create_repo() | |||
|
52 | ||||
|
53 | commit1 = commit_change( | |||
|
54 | repo.repo_name, filename=f_path, content=commit1_content, | |||
|
55 | message='A', vcs_type=backend.alias, parent=None, newfile=True) | |||
|
56 | ||||
|
57 | commit2 = commit_change( | |||
|
58 | repo.repo_name, filename=f_path, content=commit2_content, | |||
|
59 | message='B, child of A', vcs_type=backend.alias, parent=commit1) | |||
|
60 | ||||
|
61 | compare_url = url( | |||
|
62 | 'compare_url', | |||
|
63 | repo_name=repo.repo_name, | |||
|
64 | source_ref_type='rev', | |||
|
65 | source_ref=commit1.raw_id, | |||
|
66 | target_repo=repo.repo_name, | |||
|
67 | target_ref_type='rev', | |||
|
68 | target_ref=commit2.raw_id, | |||
|
69 | f_path=f_path, | |||
|
70 | diffmode='sidebyside') | |||
|
71 | ||||
|
72 | response = self.app.get(compare_url) | |||
|
73 | ||||
|
74 | response.mustcontain('Expand 1 commit') | |||
|
75 | response.mustcontain('1 file changed') | |||
|
76 | ||||
|
77 | response.mustcontain( | |||
|
78 | 'r%s:%s...r%s:%s' % ( | |||
|
79 | commit1.idx, commit1.short_id, commit2.idx, commit2.short_id)) | |||
|
80 | ||||
|
81 | response.mustcontain('<strong>{}</strong>'.format(f_path)) | |||
|
82 | ||||
|
83 | def test_diff_side_by_side_with_empty_file(self, app, backend, backend_stub): | |||
|
84 | commits = [ | |||
|
85 | {'message': 'First commit'}, | |||
|
86 | {'message': 'Commit with binary', | |||
|
87 | 'added': [nodes.FileNode('file.empty', content='')]}, | |||
|
88 | ] | |||
|
89 | f_path = 'file.empty' | |||
|
90 | repo = backend.create_repo(commits=commits) | |||
|
91 | commit1 = repo.get_commit(commit_idx=0) | |||
|
92 | commit2 = repo.get_commit(commit_idx=1) | |||
|
93 | ||||
|
94 | compare_url = url( | |||
|
95 | 'compare_url', | |||
|
96 | repo_name=repo.repo_name, | |||
|
97 | source_ref_type='rev', | |||
|
98 | source_ref=commit1.raw_id, | |||
|
99 | target_repo=repo.repo_name, | |||
|
100 | target_ref_type='rev', | |||
|
101 | target_ref=commit2.raw_id, | |||
|
102 | f_path=f_path, | |||
|
103 | diffmode='sidebyside') | |||
|
104 | ||||
|
105 | response = self.app.get(compare_url) | |||
|
106 | ||||
|
107 | response.mustcontain('Expand 1 commit') | |||
|
108 | response.mustcontain('1 file changed') | |||
|
109 | ||||
|
110 | response.mustcontain( | |||
|
111 | 'r%s:%s...r%s:%s' % ( | |||
|
112 | commit1.idx, commit1.short_id, commit2.idx, commit2.short_id)) | |||
|
113 | ||||
|
114 | response.mustcontain('<strong>{}</strong>'.format(f_path)) | |||
|
115 | ||||
|
116 | def test_diff_sidebyside_two_commits(self, app, backend): | |||
|
117 | commit_id_range = { | |||
|
118 | 'hg': { | |||
|
119 | 'commits': ['25d7e49c18b159446cadfa506a5cf8ad1cb04067', | |||
|
120 | '603d6c72c46d953420c89d36372f08d9f305f5dd'], | |||
|
121 | 'changes': '21 files changed: 943 inserted, 288 deleted' | |||
|
122 | }, | |||
|
123 | 'git': { | |||
|
124 | 'commits': ['6fc9270775aaf5544c1deb014f4ddd60c952fcbb', | |||
|
125 | '03fa803d7e9fb14daa9a3089e0d1494eda75d986'], | |||
|
126 | 'changes': '21 files changed: 943 inserted, 288 deleted' | |||
|
127 | }, | |||
|
128 | ||||
|
129 | 'svn': { | |||
|
130 | 'commits': ['336', | |||
|
131 | '337'], | |||
|
132 | 'changes': '21 files changed: 943 inserted, 288 deleted' | |||
|
133 | }, | |||
|
134 | } | |||
|
135 | ||||
|
136 | commit_info = commit_id_range[backend.alias] | |||
|
137 | commit2, commit1 = commit_info['commits'] | |||
|
138 | file_changes = commit_info['changes'] | |||
|
139 | ||||
|
140 | compare_url = url( | |||
|
141 | 'compare_url', | |||
|
142 | repo_name=backend.repo_name, | |||
|
143 | source_ref_type='rev', | |||
|
144 | source_ref=commit2, | |||
|
145 | target_repo=backend.repo_name, | |||
|
146 | target_ref_type='rev', | |||
|
147 | target_ref=commit1, | |||
|
148 | diffmode='sidebyside') | |||
|
149 | response = self.app.get(compare_url) | |||
|
150 | ||||
|
151 | response.mustcontain('Expand 1 commit') | |||
|
152 | response.mustcontain(file_changes) | |||
|
153 | ||||
|
154 | def test_diff_sidebyside_two_commits_single_file(self, app, backend): | |||
|
155 | commit_id_range = { | |||
|
156 | 'hg': { | |||
|
157 | 'commits': ['25d7e49c18b159446cadfa506a5cf8ad1cb04067', | |||
|
158 | '603d6c72c46d953420c89d36372f08d9f305f5dd'], | |||
|
159 | 'changes': '1 file changed: 1 inserted, 1 deleted' | |||
|
160 | }, | |||
|
161 | 'git': { | |||
|
162 | 'commits': ['6fc9270775aaf5544c1deb014f4ddd60c952fcbb', | |||
|
163 | '03fa803d7e9fb14daa9a3089e0d1494eda75d986'], | |||
|
164 | 'changes': '1 file changed: 1 inserted, 1 deleted' | |||
|
165 | }, | |||
|
166 | ||||
|
167 | 'svn': { | |||
|
168 | 'commits': ['336', | |||
|
169 | '337'], | |||
|
170 | 'changes': '1 file changed: 1 inserted, 1 deleted' | |||
|
171 | }, | |||
|
172 | } | |||
|
173 | f_path = 'docs/conf.py' | |||
|
174 | ||||
|
175 | commit_info = commit_id_range[backend.alias] | |||
|
176 | commit2, commit1 = commit_info['commits'] | |||
|
177 | file_changes = commit_info['changes'] | |||
|
178 | ||||
|
179 | compare_url = url( | |||
|
180 | 'compare_url', | |||
|
181 | repo_name=backend.repo_name, | |||
|
182 | source_ref_type='rev', | |||
|
183 | source_ref=commit2, | |||
|
184 | target_repo=backend.repo_name, | |||
|
185 | target_ref_type='rev', | |||
|
186 | target_ref=commit1, | |||
|
187 | f_path=f_path, | |||
|
188 | diffmode='sidebyside') | |||
|
189 | response = self.app.get(compare_url) | |||
|
190 | ||||
|
191 | response.mustcontain('Expand 1 commit') | |||
|
192 | response.mustcontain(file_changes) |
@@ -1066,7 +1066,7 b' def make_map(config):' | |||||
1066 | '/{repo_name}/annotate/{revision}/{f_path}', |
|
1066 | '/{repo_name}/annotate/{revision}/{f_path}', | |
1067 | controller='files', action='index', revision='tip', |
|
1067 | controller='files', action='index', revision='tip', | |
1068 | f_path='', annotate=True, conditions={'function': check_repo}, |
|
1068 | f_path='', annotate=True, conditions={'function': check_repo}, | |
1069 | requirements=URL_NAME_REQUIREMENTS) |
|
1069 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
1070 |
|
1070 | |||
1071 | rmap.connect('files_edit', |
|
1071 | rmap.connect('files_edit', | |
1072 | '/{repo_name}/edit/{revision}/{f_path}', |
|
1072 | '/{repo_name}/edit/{revision}/{f_path}', |
@@ -90,6 +90,7 b' class CompareController(BaseRepoControll' | |||||
90 | c.target_ref_type = "" |
|
90 | c.target_ref_type = "" | |
91 | c.commit_statuses = ChangesetStatus.STATUSES |
|
91 | c.commit_statuses = ChangesetStatus.STATUSES | |
92 | c.preview_mode = False |
|
92 | c.preview_mode = False | |
|
93 | c.file_path = None | |||
93 | return render('compare/compare_diff.html') |
|
94 | return render('compare/compare_diff.html') | |
94 |
|
95 | |||
95 | @LoginRequired() |
|
96 | @LoginRequired() | |
@@ -103,8 +104,10 b' class CompareController(BaseRepoControll' | |||||
103 |
|
104 | |||
104 | # target_ref will be evaluated in target_repo |
|
105 | # target_ref will be evaluated in target_repo | |
105 | target_repo_name = request.GET.get('target_repo', source_repo_name) |
|
106 | target_repo_name = request.GET.get('target_repo', source_repo_name) | |
106 |
target_path, target_id = parse_path_ref( |
|
107 | target_path, target_id = parse_path_ref( | |
|
108 | target_ref, default_path=request.GET.get('f_path', '')) | |||
107 |
|
109 | |||
|
110 | c.file_path = target_path | |||
108 | c.commit_statuses = ChangesetStatus.STATUSES |
|
111 | c.commit_statuses = ChangesetStatus.STATUSES | |
109 |
|
112 | |||
110 | # if merge is True |
|
113 | # if merge is True | |
@@ -115,7 +118,6 b' class CompareController(BaseRepoControll' | |||||
115 | # if merge is False |
|
118 | # if merge is False | |
116 | # Show a raw diff of source/target refs even if no ancestor exists |
|
119 | # Show a raw diff of source/target refs even if no ancestor exists | |
117 |
|
120 | |||
118 |
|
||||
119 | # c.fulldiff disables cut_off_limit |
|
121 | # c.fulldiff disables cut_off_limit | |
120 | c.fulldiff = str2bool(request.GET.get('fulldiff')) |
|
122 | c.fulldiff = str2bool(request.GET.get('fulldiff')) | |
121 |
|
123 | |||
@@ -131,7 +133,8 b' class CompareController(BaseRepoControll' | |||||
131 | target_repo=source_repo_name, |
|
133 | target_repo=source_repo_name, | |
132 | target_ref_type=source_ref_type, |
|
134 | target_ref_type=source_ref_type, | |
133 | target_ref=source_ref, |
|
135 | target_ref=source_ref, | |
134 |
merge=merge and '1' or '' |
|
136 | merge=merge and '1' or '', | |
|
137 | f_path=target_path) | |||
135 |
|
138 | |||
136 | source_repo = Repository.get_by_repo_name(source_repo_name) |
|
139 | source_repo = Repository.get_by_repo_name(source_repo_name) | |
137 | target_repo = Repository.get_by_repo_name(target_repo_name) |
|
140 | target_repo = Repository.get_by_repo_name(target_repo_name) | |
@@ -151,8 +154,11 b' class CompareController(BaseRepoControll' | |||||
151 | h.flash(msg, category='error') |
|
154 | h.flash(msg, category='error') | |
152 | return redirect(url('compare_home', repo_name=c.repo_name)) |
|
155 | return redirect(url('compare_home', repo_name=c.repo_name)) | |
153 |
|
156 | |||
154 |
source_ |
|
157 | source_scm = source_repo.scm_instance() | |
155 |
target_ |
|
158 | target_scm = target_repo.scm_instance() | |
|
159 | ||||
|
160 | source_alias = source_scm.alias | |||
|
161 | target_alias = target_scm.alias | |||
156 | if source_alias != target_alias: |
|
162 | if source_alias != target_alias: | |
157 | msg = _('The comparison of two different kinds of remote repos ' |
|
163 | msg = _('The comparison of two different kinds of remote repos ' | |
158 | 'is not available') |
|
164 | 'is not available') | |
@@ -175,9 +181,6 b' class CompareController(BaseRepoControll' | |||||
175 | c.source_ref_type = source_ref_type |
|
181 | c.source_ref_type = source_ref_type | |
176 | c.target_ref_type = target_ref_type |
|
182 | c.target_ref_type = target_ref_type | |
177 |
|
183 | |||
178 | source_scm = source_repo.scm_instance() |
|
|||
179 | target_scm = target_repo.scm_instance() |
|
|||
180 |
|
||||
181 | pre_load = ["author", "branch", "date", "message"] |
|
184 | pre_load = ["author", "branch", "date", "message"] | |
182 | c.ancestor = None |
|
185 | c.ancestor = None | |
183 | try: |
|
186 | try: | |
@@ -199,9 +202,9 b' class CompareController(BaseRepoControll' | |||||
199 | c.statuses = c.rhodecode_db_repo.statuses( |
|
202 | c.statuses = c.rhodecode_db_repo.statuses( | |
200 | [x.raw_id for x in c.commit_ranges]) |
|
203 | [x.raw_id for x in c.commit_ranges]) | |
201 |
|
204 | |||
202 | if partial: # for PR ajax commits loader |
|
205 | if partial: # for PR ajax commits loader | |
203 | if not c.ancestor: |
|
206 | if not c.ancestor: | |
204 | return '' # cannot merge if there is no ancestor |
|
207 | return '' # cannot merge if there is no ancestor | |
205 | return render('compare/compare_commits.html') |
|
208 | return render('compare/compare_commits.html') | |
206 |
|
209 | |||
207 | if c.ancestor: |
|
210 | if c.ancestor: | |
@@ -238,7 +241,8 b' class CompareController(BaseRepoControll' | |||||
238 |
|
241 | |||
239 | txtdiff = source_repo.scm_instance().get_diff( |
|
242 | txtdiff = source_repo.scm_instance().get_diff( | |
240 | commit1=source_commit, commit2=target_commit, |
|
243 | commit1=source_commit, commit2=target_commit, | |
241 |
path1=source_path |
|
244 | path=target_path, path1=source_path) | |
|
245 | ||||
242 | diff_processor = diffs.DiffProcessor( |
|
246 | diff_processor = diffs.DiffProcessor( | |
243 | txtdiff, format='newdiff', diff_limit=diff_limit, |
|
247 | txtdiff, format='newdiff', diff_limit=diff_limit, | |
244 | file_limit=file_limit, show_full_diff=c.fulldiff) |
|
248 | file_limit=file_limit, show_full_diff=c.fulldiff) | |
@@ -260,5 +264,7 b' class CompareController(BaseRepoControll' | |||||
260 | ).render_patchset(_parsed, source_ref, target_ref) |
|
264 | ).render_patchset(_parsed, source_ref, target_ref) | |
261 |
|
265 | |||
262 | c.preview_mode = merge |
|
266 | c.preview_mode = merge | |
|
267 | c.source_commit = source_commit | |||
|
268 | c.target_commit = target_commit | |||
263 |
|
269 | |||
264 | return render('compare/compare_diff.html') |
|
270 | return render('compare/compare_diff.html') |
@@ -799,21 +799,15 b' class FilesController(BaseRepoController' | |||||
799 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
799 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
800 | 'repository.admin') |
|
800 | 'repository.admin') | |
801 | def diff(self, repo_name, f_path): |
|
801 | def diff(self, repo_name, f_path): | |
802 | ignore_whitespace = request.GET.get('ignorews') == '1' |
|
802 | ||
803 |
|
|
803 | c.action = request.GET.get('diff') | |
804 | diff1 = request.GET.get('diff1', '') |
|
804 | diff1 = request.GET.get('diff1', '') | |
|
805 | diff2 = request.GET.get('diff2', '') | |||
805 |
|
806 | |||
806 | path1, diff1 = parse_path_ref(diff1, default_path=f_path) |
|
807 | path1, diff1 = parse_path_ref(diff1, default_path=f_path) | |
807 |
|
808 | |||
808 | diff2 = request.GET.get('diff2', '') |
|
809 | ignore_whitespace = str2bool(request.GET.get('ignorews')) | |
809 |
|
|
810 | line_context = request.GET.get('context', 3) | |
810 | c.no_changes = diff1 == diff2 |
|
|||
811 | c.f_path = f_path |
|
|||
812 | c.big_diff = False |
|
|||
813 | c.ignorews_url = _ignorews_url |
|
|||
814 | c.context_url = _context_url |
|
|||
815 | c.changes = OrderedDict() |
|
|||
816 | c.changes[diff2] = [] |
|
|||
817 |
|
811 | |||
818 | if not any((diff1, diff2)): |
|
812 | if not any((diff1, diff2)): | |
819 | h.flash( |
|
813 | h.flash( | |
@@ -821,18 +815,16 b' class FilesController(BaseRepoController' | |||||
821 | category='error') |
|
815 | category='error') | |
822 | raise HTTPBadRequest() |
|
816 | raise HTTPBadRequest() | |
823 |
|
817 | |||
824 | # special case if we want a show commit_id only, it's impl here |
|
818 | if c.action not in ['download', 'raw']: | |
825 | # to reduce JS and callbacks |
|
819 | # redirect to new view if we render diff | |
826 |
|
820 | return redirect( | ||
827 | if request.GET.get('show_rev') and diff1: |
|
821 | url('compare_url', repo_name=repo_name, | |
828 | if str2bool(request.GET.get('annotate', 'False')): |
|
822 | source_ref_type='rev', | |
829 | _url = url('files_annotate_home', repo_name=c.repo_name, |
|
823 | source_ref=diff1, | |
830 | revision=diff1, f_path=path1) |
|
824 | target_repo=c.repo_name, | |
831 | else: |
|
825 | target_ref_type='rev', | |
832 | _url = url('files_home', repo_name=c.repo_name, |
|
826 | target_ref=diff2, | |
833 |
|
|
827 | f_path=f_path)) | |
834 |
|
||||
835 | return redirect(_url) |
|
|||
836 |
|
828 | |||
837 | try: |
|
829 | try: | |
838 | node1 = self._get_file_node(diff1, path1) |
|
830 | node1 = self._get_file_node(diff1, path1) | |
@@ -877,98 +869,40 b' class FilesController(BaseRepoController' | |||||
877 | return diff.as_raw() |
|
869 | return diff.as_raw() | |
878 |
|
870 | |||
879 | else: |
|
871 | else: | |
880 | fid = h.FID(diff2, node2.path) |
|
872 | return redirect( | |
881 | line_context_lcl = get_line_ctx(fid, request.GET) |
|
873 | url('compare_url', repo_name=repo_name, | |
882 | ign_whitespace_lcl = get_ignore_ws(fid, request.GET) |
|
874 | source_ref_type='rev', | |
883 |
|
875 | source_ref=diff1, | ||
884 | __, commit1, commit2, diff, st, data = diffs.wrapped_diff( |
|
876 | target_repo=c.repo_name, | |
885 | filenode_old=node1, |
|
877 | target_ref_type='rev', | |
886 | filenode_new=node2, |
|
878 | target_ref=diff2, | |
887 | diff_limit=self.cut_off_limit_diff, |
|
879 | f_path=f_path)) | |
888 | file_limit=self.cut_off_limit_file, |
|
|||
889 | show_full_diff=request.GET.get('fulldiff'), |
|
|||
890 | ignore_whitespace=ign_whitespace_lcl, |
|
|||
891 | line_context=line_context_lcl,) |
|
|||
892 |
|
||||
893 | c.lines_added = data['stats']['added'] if data else 0 |
|
|||
894 | c.lines_deleted = data['stats']['deleted'] if data else 0 |
|
|||
895 | c.files = [data] |
|
|||
896 | c.commit_ranges = [c.commit_1, c.commit_2] |
|
|||
897 | c.ancestor = None |
|
|||
898 | c.statuses = [] |
|
|||
899 | c.target_repo = c.rhodecode_db_repo |
|
|||
900 | c.filename1 = node1.path |
|
|||
901 | c.filename = node2.path |
|
|||
902 | c.binary_file = node1.is_binary or node2.is_binary |
|
|||
903 | operation = data['operation'] if data else '' |
|
|||
904 |
|
||||
905 | commit_changes = { |
|
|||
906 | # TODO: it's passing the old file to the diff to keep the |
|
|||
907 | # standard but this is not being used for this template, |
|
|||
908 | # but might need both files in the future or a more standard |
|
|||
909 | # way to work with that |
|
|||
910 | 'fid': [commit1, commit2, operation, |
|
|||
911 | c.filename, diff, st, data] |
|
|||
912 | } |
|
|||
913 |
|
||||
914 | c.changes = commit_changes |
|
|||
915 |
|
||||
916 | return render('files/file_diff.html') |
|
|||
917 |
|
880 | |||
918 | @LoginRequired() |
|
881 | @LoginRequired() | |
919 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
882 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', | |
920 | 'repository.admin') |
|
883 | 'repository.admin') | |
921 | def diff_2way(self, repo_name, f_path): |
|
884 | def diff_2way(self, repo_name, f_path): | |
|
885 | """ | |||
|
886 | Kept only to make OLD links work | |||
|
887 | """ | |||
922 | diff1 = request.GET.get('diff1', '') |
|
888 | diff1 = request.GET.get('diff1', '') | |
923 | diff2 = request.GET.get('diff2', '') |
|
889 | diff2 = request.GET.get('diff2', '') | |
924 |
|
890 | |||
925 | nodes = [] |
|
891 | if not any((diff1, diff2)): | |
926 | unknown_commits = [] |
|
892 | h.flash( | |
927 | for commit in [diff1, diff2]: |
|
893 | 'Need query parameter "diff1" or "diff2" to generate a diff.', | |
928 |
|
|
894 | category='error') | |
929 | nodes.append(self._get_file_node(commit, f_path)) |
|
895 | raise HTTPBadRequest() | |
930 | except (RepositoryError, NodeError): |
|
|||
931 | log.exception('%(commit)s does not exist' % {'commit': commit}) |
|
|||
932 | unknown_commits.append(commit) |
|
|||
933 | h.flash(h.literal( |
|
|||
934 | _('Commit %(commit)s does not exist.') % {'commit': commit} |
|
|||
935 | ), category='error') |
|
|||
936 |
|
||||
937 | if unknown_commits: |
|
|||
938 | return redirect(url('files_home', repo_name=c.repo_name, |
|
|||
939 | f_path=f_path)) |
|
|||
940 |
|
||||
941 | if all(isinstance(node.commit, EmptyCommit) for node in nodes): |
|
|||
942 | raise HTTPNotFound |
|
|||
943 |
|
||||
944 | node1, node2 = nodes |
|
|||
945 |
|
896 | |||
946 | f_gitdiff = diffs.get_gitdiff(node1, node2, ignore_whitespace=False) |
|
897 | return redirect( | |
947 | diff_processor = diffs.DiffProcessor(f_gitdiff, format='gitdiff') |
|
898 | url('compare_url', repo_name=repo_name, | |
948 | diff_data = diff_processor.prepare() |
|
899 | source_ref_type='rev', | |
949 |
|
900 | source_ref=diff1, | ||
950 | if not diff_data or diff_data[0]['raw_diff'] == '': |
|
901 | target_repo=c.repo_name, | |
951 | h.flash(h.literal(_('%(file_path)s has not changed ' |
|
902 | target_ref_type='rev', | |
952 | 'between %(commit_1)s and %(commit_2)s.') % { |
|
903 | target_ref=diff2, | |
953 |
|
|
904 | f_path=f_path, | |
954 | 'commit_1': node1.commit.id, |
|
905 | diffmode='sideside')) | |
955 | 'commit_2': node2.commit.id |
|
|||
956 | }), category='error') |
|
|||
957 | return redirect(url('files_home', repo_name=c.repo_name, |
|
|||
958 | f_path=f_path)) |
|
|||
959 |
|
||||
960 | c.diff_data = diff_data[0] |
|
|||
961 | c.FID = h.FID(diff2, node2.path) |
|
|||
962 | # cleanup some unneeded data |
|
|||
963 | del c.diff_data['raw_diff'] |
|
|||
964 | del c.diff_data['chunks'] |
|
|||
965 |
|
||||
966 | c.node1 = node1 |
|
|||
967 | c.commit_1 = node1.commit |
|
|||
968 | c.node2 = node2 |
|
|||
969 | c.commit_2 = node2.commit |
|
|||
970 |
|
||||
971 | return render('files/diff_2way.html') |
|
|||
972 |
|
906 | |||
973 | def _get_file_node(self, commit_id, f_path): |
|
907 | def _get_file_node(self, commit_id, f_path): | |
974 | if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]: |
|
908 | if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]: |
@@ -27,6 +27,7 b' Should only contain utilities to be shar' | |||||
27 | from rhodecode.lib import helpers as h |
|
27 | from rhodecode.lib import helpers as h | |
28 | from rhodecode.lib.vcs.exceptions import RepositoryError |
|
28 | from rhodecode.lib.vcs.exceptions import RepositoryError | |
29 |
|
29 | |||
|
30 | ||||
30 | def parse_path_ref(ref, default_path=None): |
|
31 | def parse_path_ref(ref, default_path=None): | |
31 | """ |
|
32 | """ | |
32 | Parse out a path and reference combination and return both parts of it. |
|
33 | Parse out a path and reference combination and return both parts of it. | |
@@ -76,8 +77,8 b' def get_commit_from_ref_name(repo, ref_n' | |||||
76 | } |
|
77 | } | |
77 |
|
78 | |||
78 | commit_id = ref_name |
|
79 | commit_id = ref_name | |
79 | if repo_scm.alias != 'svn': # pass svn refs straight to backend until |
|
80 | if repo_scm.alias != 'svn': # pass svn refs straight to backend until | |
80 | # the branch issue with svn is fixed |
|
81 | # the branch issue with svn is fixed | |
81 | if ref_type and ref_type in ref_type_mapping: |
|
82 | if ref_type and ref_type in ref_type_mapping: | |
82 | try: |
|
83 | try: | |
83 | commit_id = ref_type_mapping[ref_type][ref_name] |
|
84 | commit_id = ref_type_mapping[ref_type][ref_name] |
@@ -378,6 +378,7 b' class BaseRepository(object):' | |||||
378 | parameter works only for backends which support diff generation for |
|
378 | parameter works only for backends which support diff generation for | |
379 | different paths. Other backends will raise a `ValueError` if `path1` |
|
379 | different paths. Other backends will raise a `ValueError` if `path1` | |
380 | is set and has a different value than `path`. |
|
380 | is set and has a different value than `path`. | |
|
381 | :param file_path: filter this diff by given path pattern | |||
381 | """ |
|
382 | """ | |
382 | raise NotImplementedError |
|
383 | raise NotImplementedError | |
383 |
|
384 | |||
@@ -1540,9 +1541,10 b' class Diff(object):' | |||||
1540 | """ |
|
1541 | """ | |
1541 | Represents a diff result from a repository backend. |
|
1542 | Represents a diff result from a repository backend. | |
1542 |
|
1543 | |||
1543 |
Subclasses have to provide a backend specific value for |
|
1544 | Subclasses have to provide a backend specific value for | |
|
1545 | :attr:`_header_re` and :attr:`_meta_re`. | |||
1544 | """ |
|
1546 | """ | |
1545 |
|
1547 | _meta_re = None | ||
1546 | _header_re = None |
|
1548 | _header_re = None | |
1547 |
|
1549 | |||
1548 | def __init__(self, raw_diff): |
|
1550 | def __init__(self, raw_diff): | |
@@ -1554,10 +1556,19 b' class Diff(object):' | |||||
1554 | to make diffs consistent we must prepend with \n, and make sure |
|
1556 | to make diffs consistent we must prepend with \n, and make sure | |
1555 | we can detect last chunk as this was also has special rule |
|
1557 | we can detect last chunk as this was also has special rule | |
1556 | """ |
|
1558 | """ | |
1557 | chunks = ('\n' + self.raw).split('\ndiff --git')[1:] |
|
1559 | ||
|
1560 | diff_parts = ('\n' + self.raw).split('\ndiff --git') | |||
|
1561 | header = diff_parts[0] | |||
|
1562 | ||||
|
1563 | if self._meta_re: | |||
|
1564 | match = self._meta_re.match(header) | |||
|
1565 | ||||
|
1566 | chunks = diff_parts[1:] | |||
1558 | total_chunks = len(chunks) |
|
1567 | total_chunks = len(chunks) | |
1559 | return (DiffChunk(chunk, self, cur_chunk == total_chunks) |
|
1568 | ||
1560 | for cur_chunk, chunk in enumerate(chunks, start=1)) |
|
1569 | return ( | |
|
1570 | DiffChunk(chunk, self, cur_chunk == total_chunks) | |||
|
1571 | for cur_chunk, chunk in enumerate(chunks, start=1)) | |||
1561 |
|
1572 | |||
1562 |
|
1573 | |||
1563 | class DiffChunk(object): |
|
1574 | class DiffChunk(object): |
@@ -30,6 +30,10 b' from rhodecode.lib.vcs.backends import b' | |||||
30 |
|
30 | |||
31 | class SubversionDiff(base.Diff): |
|
31 | class SubversionDiff(base.Diff): | |
32 |
|
32 | |||
|
33 | _meta_re = re.compile(r""" | |||
|
34 | (?:^(?P<svn_bin_patch>Cannot[ ]display:[ ]file[ ]marked[ ]as[ ]a[ ]binary[ ]type.)(?:\n|$))? | |||
|
35 | """, re.VERBOSE | re.MULTILINE) | |||
|
36 | ||||
33 | _header_re = re.compile(r""" |
|
37 | _header_re = re.compile(r""" | |
34 | #^diff[ ]--git |
|
38 | #^diff[ ]--git | |
35 | [ ]"?a/(?P<a_path>.+?)"?[ ]"?b/(?P<b_path>.+?)"?\n |
|
39 | [ ]"?a/(?P<a_path>.+?)"?[ ]"?b/(?P<b_path>.+?)"?\n |
@@ -1477,8 +1477,9 b' table.integrations {' | |||||
1477 | margin-left: 8px; |
|
1477 | margin-left: 8px; | |
1478 | } |
|
1478 | } | |
1479 |
|
1479 | |||
1480 |
|
|
1480 | div.ancestor { | |
1481 | margin: @padding 0; |
|
1481 | margin: @padding 0; | |
|
1482 | line-height: 3.0em; | |||
1482 | } |
|
1483 | } | |
1483 |
|
1484 | |||
1484 | .cs_icon_td input[type="checkbox"] { |
|
1485 | .cs_icon_td input[type="checkbox"] { |
@@ -72,6 +72,7 b'' | |||||
72 | } |
|
72 | } | |
73 | .disabled { |
|
73 | .disabled { | |
74 | opacity: .5; |
|
74 | opacity: .5; | |
|
75 | cursor: inherit; | |||
75 | } |
|
76 | } | |
76 | .help-block { |
|
77 | .help-block { | |
77 | color: inherit; |
|
78 | color: inherit; |
@@ -73,6 +73,16 b' String.prototype.capitalizeFirstLetter =' | |||||
73 | }; |
|
73 | }; | |
74 |
|
74 | |||
75 |
|
75 | |||
|
76 | String.prototype.truncateAfter = function(chars, suffix) { | |||
|
77 | var suffix = suffix || ''; | |||
|
78 | if (this.length > chars) { | |||
|
79 | return this.substr(0, chars) + suffix; | |||
|
80 | } else { | |||
|
81 | return this; | |||
|
82 | } | |||
|
83 | }; | |||
|
84 | ||||
|
85 | ||||
76 | /** |
|
86 | /** | |
77 | * Splits remainder |
|
87 | * Splits remainder | |
78 | * |
|
88 | * |
@@ -112,7 +112,7 b'' | |||||
112 |
|
112 | |||
113 | <div class="fieldset"> |
|
113 | <div class="fieldset"> | |
114 | <div class="left-label"> |
|
114 | <div class="left-label"> | |
115 | ${_('Diffs')}: |
|
115 | ${_('Diff options')}: | |
116 | </div> |
|
116 | </div> | |
117 | <div class="right-content"> |
|
117 | <div class="right-content"> | |
118 | <div class="diff-actions"> |
|
118 | <div class="diff-actions"> |
@@ -29,29 +29,84 b'' | |||||
29 | </%def> |
|
29 | </%def> | |
30 |
|
30 | |||
31 | <%def name="main()"> |
|
31 | <%def name="main()"> | |
32 | <div class="summary-header"> |
|
32 | <div class="summary-header"> | |
33 | <div class="title"> |
|
33 | <div class="title"> | |
34 | <div class="title-content"> |
|
|||
35 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
34 | ${self.repo_page_title(c.rhodecode_db_repo)} | |
36 | </div> |
|
|||
37 | </div> |
|
|||
38 | <div class="header-buttons"> |
|
|||
39 | <a href="${h.url('compare_url', repo_name=c.repo_name, source_ref_type='rev', source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'), target_ref_type='rev', target_ref=c.commit_ranges[-1].raw_id)}" |
|
|||
40 | class="btn btn-default"> |
|
|||
41 | ${_('Show combined compare')} |
|
|||
42 | </a> |
|
|||
43 | </div> |
|
35 | </div> | |
44 | </div> |
|
36 | </div> | |
|
37 | ||||
|
38 | ||||
|
39 | <div class="summary changeset"> | |||
|
40 | <div class="summary-detail"> | |||
|
41 | <div class="summary-detail-header"> | |||
|
42 | <span class="breadcrumbs files_location"> | |||
|
43 | <h4> | |||
|
44 | ${_('Commit Range')} | |||
|
45 | <code> | |||
|
46 | r${c.commit_ranges[0].revision}:${h.short_id(c.commit_ranges[0].raw_id)}...r${c.commit_ranges[-1].revision}:${h.short_id(c.commit_ranges[-1].raw_id)} | |||
|
47 | </code> | |||
|
48 | </h4> | |||
|
49 | </span> | |||
|
50 | </div> | |||
|
51 | ||||
|
52 | <div class="fieldset"> | |||
|
53 | <div class="left-label"> | |||
|
54 | ${_('Diff option')}: | |||
|
55 | </div> | |||
|
56 | <div class="right-content"> | |||
|
57 | <div class="header-buttons"> | |||
|
58 | <a href="${h.url('compare_url', repo_name=c.repo_name, source_ref_type='rev', source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'), target_ref_type='rev', target_ref=c.commit_ranges[-1].raw_id)}"> | |||
|
59 | ${_('Show combined compare')} | |||
|
60 | </a> | |||
|
61 | </div> | |||
|
62 | </div> | |||
|
63 | </div> | |||
45 |
|
64 | |||
46 | <div class="summary-detail"> |
|
65 | <%doc> | |
47 | <div class="title"> |
|
66 | ##TODO(marcink): implement this and diff menus | |
48 | <h2> |
|
67 | <div class="fieldset"> | |
49 | ${self.breadcrumbs_links()} |
|
68 | <div class="left-label"> | |
50 | </h2> |
|
69 | ${_('Diff options')}: | |
|
70 | </div> | |||
|
71 | <div class="right-content"> | |||
|
72 | <div class="diff-actions"> | |||
|
73 | <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"> | |||
|
74 | ${_('Raw Diff')} | |||
|
75 | </a> | |||
|
76 | | | |||
|
77 | <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}"> | |||
|
78 | ${_('Patch Diff')} | |||
|
79 | </a> | |||
|
80 | | | |||
|
81 | <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision='?',diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}"> | |||
|
82 | ${_('Download Diff')} | |||
|
83 | </a> | |||
|
84 | </div> | |||
|
85 | </div> | |||
|
86 | </div> | |||
|
87 | </%doc> | |||
|
88 | </div> <!-- end summary-detail --> | |||
|
89 | ||||
|
90 | </div> <!-- end summary --> | |||
|
91 | ||||
|
92 | <div id="changeset_compare_view_content"> | |||
|
93 | <div class="pull-left"> | |||
|
94 | <div class="btn-group"> | |||
|
95 | <a | |||
|
96 | class="btn" | |||
|
97 | href="#" | |||
|
98 | onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false"> | |||
|
99 | ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)} | |||
|
100 | </a> | |||
|
101 | <a | |||
|
102 | class="btn" | |||
|
103 | href="#" | |||
|
104 | onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false"> | |||
|
105 | ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)} | |||
|
106 | </a> | |||
|
107 | </div> | |||
51 | </div> |
|
108 | </div> | |
52 | </div> |
|
109 | ## Commit range generated below | |
53 | <div id="changeset_compare_view_content"> |
|
|||
54 | ##CS |
|
|||
55 | <%include file="../compare/compare_commits.html"/> |
|
110 | <%include file="../compare/compare_commits.html"/> | |
56 | <div class="cs_files"> |
|
111 | <div class="cs_files"> | |
57 | <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/> |
|
112 | <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/> | |
@@ -65,7 +120,6 b'' | |||||
65 | commit=commit, |
|
120 | commit=commit, | |
66 | )} |
|
121 | )} | |
67 | %endfor |
|
122 | %endfor | |
68 | </table> |
|
|||
69 | </div> |
|
123 | </div> | |
70 | </div> |
|
124 | </div> | |
71 | </%def> |
|
125 | </%def> |
@@ -52,45 +52,6 b'' | |||||
52 | </div> |
|
52 | </div> | |
53 | </%def> |
|
53 | </%def> | |
54 |
|
54 | |||
55 | <%def name="diff_menu(repo_name, f_path, cs1, cs2, change, file=None)"> |
|
|||
56 | <% |
|
|||
57 | onclick_diff2way = '' |
|
|||
58 | if (file and file["exceeds_limit"]): |
|
|||
59 | onclick_diff2way = '''return confirm('%s');''' % _("Showing a big diff might take some time and resources, continue?") |
|
|||
60 | %> |
|
|||
61 |
|
||||
62 | % if change in ['A', 'M']: |
|
|||
63 | <a href="${h.url('files_home',repo_name=repo_name,f_path=f_path,revision=cs2)}" |
|
|||
64 | class="tooltip" title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': cs2[:12]})}"> |
|
|||
65 | ${_('Show File')} |
|
|||
66 | </a> |
|
|||
67 | % else: |
|
|||
68 | <span |
|
|||
69 | class="tooltip" title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': cs2[:12]})}"> |
|
|||
70 | ${_('Show File')} |
|
|||
71 | </span> |
|
|||
72 | % endif |
|
|||
73 | | |
|
|||
74 | <a href="${h.url('files_diff_home',repo_name=repo_name,f_path=f_path,diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" |
|
|||
75 | class="tooltip" title="${h.tooltip(_('Show full diff for this file'))}"> |
|
|||
76 | ${_('Unified Diff')} |
|
|||
77 | </a> |
|
|||
78 | | |
|
|||
79 | <a href="${h.url('files_diff_2way_home',repo_name=repo_name,f_path=f_path,diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" |
|
|||
80 | class="tooltip" title="${h.tooltip(_('Show full side-by-side diff for this file'))}"} onclick="${onclick_diff2way}"> |
|
|||
81 | ${_('Side-by-side Diff')} |
|
|||
82 | </a> |
|
|||
83 | | |
|
|||
84 | <a href="${h.url('files_diff_home',repo_name=repo_name,f_path=f_path,diff2=cs2,diff1=cs1,diff='raw')}" |
|
|||
85 | class="tooltip" title="${h.tooltip(_('Raw diff'))}"> |
|
|||
86 | ${_('Raw Diff')} |
|
|||
87 | </a> |
|
|||
88 | | |
|
|||
89 | <a href="${h.url('files_diff_home',repo_name=repo_name,f_path=f_path,diff2=cs2,diff1=cs1,diff='download')}" |
|
|||
90 | class="tooltip" title="${h.tooltip(_('Download diff'))}"> |
|
|||
91 | ${_('Download Diff')} |
|
|||
92 | </a> |
|
|||
93 | </%def> |
|
|||
94 |
|
55 | |||
95 | <%def name="diff_summary_text(changed_files, lines_added, lines_deleted, limited_diff=False)"> |
|
56 | <%def name="diff_summary_text(changed_files, lines_added, lines_deleted, limited_diff=False)"> | |
96 | % if limited_diff: |
|
57 | % if limited_diff: |
@@ -162,10 +162,11 b' collapse_all = len(diffset.files) > coll' | |||||
162 |
|
162 | |||
163 | <div class="filediffs"> |
|
163 | <div class="filediffs"> | |
164 | %for i, filediff in enumerate(diffset.files): |
|
164 | %for i, filediff in enumerate(diffset.files): | |
165 | <% |
|
165 | ||
166 | lines_changed = filediff['patch']['stats']['added'] + filediff['patch']['stats']['deleted'] |
|
166 | <% | |
167 | over_lines_changed_limit = lines_changed > lines_changed_limit |
|
167 | lines_changed = filediff['patch']['stats']['added'] + filediff['patch']['stats']['deleted'] | |
168 | %> |
|
168 | over_lines_changed_limit = lines_changed > lines_changed_limit | |
|
169 | %> | |||
169 | <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox"> |
|
170 | <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox"> | |
170 | <div |
|
171 | <div | |
171 | class="filediff" |
|
172 | class="filediff" | |
@@ -414,6 +415,7 b' from rhodecode.lib.diffs import NEW_FILE' | |||||
414 | if line.modified.lineno: |
|
415 | if line.modified.lineno: | |
415 | new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, line.modified.lineno, 'n') |
|
416 | new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, line.modified.lineno, 'n') | |
416 | %> |
|
417 | %> | |
|
418 | ||||
417 | <tr class="cb-line"> |
|
419 | <tr class="cb-line"> | |
418 | <td class="cb-data ${action_class(line.original.action)}" |
|
420 | <td class="cb-data ${action_class(line.original.action)}" | |
419 | data-line-number="${line.original.lineno}" |
|
421 | data-line-number="${line.original.lineno}" | |
@@ -544,6 +546,7 b' from rhodecode.lib.diffs import NEW_FILE' | |||||
544 | <div class="diffset-menu clearinner"> |
|
546 | <div class="diffset-menu clearinner"> | |
545 | <div class="pull-right"> |
|
547 | <div class="pull-right"> | |
546 | <div class="btn-group"> |
|
548 | <div class="btn-group"> | |
|
549 | ||||
547 | <a |
|
550 | <a | |
548 | class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip" |
|
551 | class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip" | |
549 | title="${_('View side by side')}" |
|
552 | title="${_('View side by side')}" | |
@@ -557,20 +560,21 b' from rhodecode.lib.diffs import NEW_FILE' | |||||
557 | </a> |
|
560 | </a> | |
558 | </div> |
|
561 | </div> | |
559 | </div> |
|
562 | </div> | |
|
563 | ||||
560 | <div class="pull-left"> |
|
564 | <div class="pull-left"> | |
561 | <div class="btn-group"> |
|
565 | <div class="btn-group"> | |
562 | <a |
|
566 | <a | |
563 | class="btn" |
|
567 | class="btn" | |
564 | href="#" |
|
568 | href="#" | |
565 | onclick="$('input[class=filediff-collapse-state]').prop('checked', false); return false">${_('Expand All')}</a> |
|
569 | onclick="$('input[class=filediff-collapse-state]').prop('checked', false); return false">${_('Expand All Files')}</a> | |
566 | <a |
|
570 | <a | |
567 | class="btn" |
|
571 | class="btn" | |
568 | href="#" |
|
572 | href="#" | |
569 | onclick="$('input[class=filediff-collapse-state]').prop('checked', true); return false">${_('Collapse All')}</a> |
|
573 | onclick="$('input[class=filediff-collapse-state]').prop('checked', true); return false">${_('Collapse All Files')}</a> | |
570 | <a |
|
574 | <a | |
571 | class="btn" |
|
575 | class="btn" | |
572 | href="#" |
|
576 | href="#" | |
573 | onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode')}</a> |
|
577 | onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode Diff')}</a> | |
574 | </div> |
|
578 | </div> | |
575 | </div> |
|
579 | </div> | |
576 | </div> |
|
580 | </div> |
@@ -1,20 +1,17 b'' | |||||
1 | ## Changesets table ! |
|
1 | ## Changesets table ! | |
2 | <%namespace name="base" file="/base/base.html"/> |
|
2 | <%namespace name="base" file="/base/base.html"/> | |
3 | <div class="container"> |
|
|||
4 | %if not c.commit_ranges: |
|
|||
5 | <p class="empty_data">${_('No Commits')}</p> |
|
|||
6 | %else: |
|
|||
7 |
|
3 | |||
8 |
|
|
4 | %if c.ancestor: | |
9 |
|
|
5 | <div class="ancestor">${_('Common Ancestor Commit')}: | |
10 |
|
|
6 | <a href="${h.url('changeset_home', | |
11 |
|
|
7 | repo_name=c.repo_name, | |
12 |
|
|
8 | revision=c.ancestor)}"> | |
13 |
|
|
9 | ${h.short_id(c.ancestor)} | |
14 |
|
|
10 | </a> | |
15 | </p> |
|
11 | </div> | |
16 |
|
|
12 | %endif | |
17 |
|
13 | |||
|
14 | <div class="container"> | |||
18 | <input type="hidden" name="__start__" value="revisions:sequence"> |
|
15 | <input type="hidden" name="__start__" value="revisions:sequence"> | |
19 | <table class="rctable compare_view_commits"> |
|
16 | <table class="rctable compare_view_commits"> | |
20 | <tr> |
|
17 | <tr> | |
@@ -66,9 +63,21 b'' | |||||
66 | </td> |
|
63 | </td> | |
67 | </tr> |
|
64 | </tr> | |
68 | %endfor |
|
65 | %endfor | |
|
66 | <tr class="compare_select_hidden" style="display: none"> | |||
|
67 | <td colspan="5"> | |||
|
68 | ${ungettext('%s commit hidden','%s commits hidden', len(c.commit_ranges)) % len(c.commit_ranges)} | |||
|
69 | </td> | |||
|
70 | </tr> | |||
|
71 | % if not c.commit_ranges: | |||
|
72 | <tr class="compare_select"> | |||
|
73 | <td colspan="5"> | |||
|
74 | ${_('No commits in this compare')} | |||
|
75 | </td> | |||
|
76 | </tr> | |||
|
77 | % endif | |||
69 | </table> |
|
78 | </table> | |
70 | <input type="hidden" name="__end__" value="revisions:sequence"> |
|
79 | <input type="hidden" name="__end__" value="revisions:sequence"> | |
71 | %endif |
|
80 | ||
72 | </div> |
|
81 | </div> | |
73 |
|
82 | |||
74 | <script> |
|
83 | <script> | |
@@ -76,7 +85,7 b'' | |||||
76 | var target_expand = $(this); |
|
85 | var target_expand = $(this); | |
77 | var cid = target_expand.data('commitId'); |
|
86 | var cid = target_expand.data('commitId'); | |
78 |
|
87 | |||
79 | ## TODO: dan: extract styles into css, and just toggleClass('open') here |
|
88 | // ## TODO: dan: extract styles into css, and just toggleClass('open') here | |
80 | if (target_expand.hasClass('open')){ |
|
89 | if (target_expand.hasClass('open')){ | |
81 | $('#c-'+cid).css({ |
|
90 | $('#c-'+cid).css({ | |
82 | 'height': '1.5em', |
|
91 | 'height': '1.5em', |
@@ -34,34 +34,132 b'' | |||||
34 | <div class="box"> |
|
34 | <div class="box"> | |
35 | <div class="title"> |
|
35 | <div class="title"> | |
36 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
36 | ${self.repo_page_title(c.rhodecode_db_repo)} | |
37 | <div class="breadcrumbs"> |
|
|||
38 | ${_('Compare Commits')} |
|
|||
39 | </div> |
|
|||
40 | </div> |
|
37 | </div> | |
41 |
|
38 | |||
|
39 | <div class="summary changeset"> | |||
|
40 | <div class="summary-detail"> | |||
|
41 | <div class="summary-detail-header"> | |||
|
42 | <span class="breadcrumbs files_location"> | |||
|
43 | <h4> | |||
|
44 | ${_('Compare Commits')} | |||
|
45 | % if c.file_path: | |||
|
46 | ${_('for file')} <a href="#${'a_' + h.FID('',c.file_path)}">${c.file_path}</a> | |||
|
47 | % endif | |||
|
48 | ||||
|
49 | % if c.commit_ranges: | |||
|
50 | <code> | |||
|
51 | r${c.source_commit.revision}:${h.short_id(c.source_commit.raw_id)}...r${c.target_commit.revision}:${h.short_id(c.target_commit.raw_id)} | |||
|
52 | </code> | |||
|
53 | % endif | |||
|
54 | </h4> | |||
|
55 | </span> | |||
|
56 | </div> | |||
|
57 | ||||
|
58 | <div class="fieldset"> | |||
|
59 | <div class="left-label"> | |||
|
60 | ${_('Target')}: | |||
|
61 | </div> | |||
|
62 | <div class="right-content"> | |||
|
63 | <div> | |||
|
64 | <div class="code-header" > | |||
|
65 | <div class="compare_header"> | |||
|
66 | ## The hidden elements are replaced with a select2 widget | |||
|
67 | ${h.hidden('compare_source')} | |||
|
68 | </div> | |||
|
69 | </div> | |||
|
70 | </div> | |||
|
71 | </div> | |||
|
72 | </div> | |||
|
73 | ||||
|
74 | <div class="fieldset"> | |||
|
75 | <div class="left-label"> | |||
|
76 | ${_('Source')}: | |||
|
77 | </div> | |||
|
78 | <div class="right-content"> | |||
|
79 | <div> | |||
|
80 | <div class="code-header" > | |||
|
81 | <div class="compare_header"> | |||
|
82 | ## The hidden elements are replaced with a select2 widget | |||
|
83 | ${h.hidden('compare_target')} | |||
|
84 | </div> | |||
|
85 | </div> | |||
|
86 | </div> | |||
|
87 | </div> | |||
|
88 | </div> | |||
|
89 | ||||
|
90 | <div class="fieldset"> | |||
|
91 | <div class="left-label"> | |||
|
92 | ${_('Actions')}: | |||
|
93 | </div> | |||
|
94 | <div class="right-content"> | |||
|
95 | <div> | |||
|
96 | <div class="code-header" > | |||
|
97 | <div class="compare_header"> | |||
|
98 | ||||
|
99 | <div class="compare-buttons"> | |||
|
100 | % if c.compare_home: | |||
|
101 | <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a> | |||
|
102 | ||||
|
103 | <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a> | |||
|
104 | <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a> | |||
|
105 | <div id="changeset_compare_view_content"> | |||
|
106 | <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div> | |||
|
107 | </div> | |||
|
108 | ||||
|
109 | % elif c.preview_mode: | |||
|
110 | <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Compare Commits')}</a> | |||
|
111 | <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a> | |||
|
112 | <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a> | |||
|
113 | ||||
|
114 | % else: | |||
|
115 | <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a> | |||
|
116 | <a id="btn-swap" class="btn btn-primary" href="${c.swap_url}">${_('Swap')}</a> | |||
|
117 | ||||
|
118 | ## allow comment only if there are commits to comment on | |||
|
119 | % if c.diffset and c.diffset.files and c.commit_ranges: | |||
|
120 | <a id="compare_changeset_status_toggle" class="btn btn-primary">${_('Comment')}</a> | |||
|
121 | % else: | |||
|
122 | <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a> | |||
|
123 | % endif | |||
|
124 | % endif | |||
|
125 | </div> | |||
|
126 | </div> | |||
|
127 | </div> | |||
|
128 | </div> | |||
|
129 | </div> | |||
|
130 | </div> | |||
|
131 | ||||
|
132 | <%doc> | |||
|
133 | ##TODO(marcink): implement this and diff menus | |||
|
134 | <div class="fieldset"> | |||
|
135 | <div class="left-label"> | |||
|
136 | ${_('Diff options')}: | |||
|
137 | </div> | |||
|
138 | <div class="right-content"> | |||
|
139 | <div class="diff-actions"> | |||
|
140 | <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Raw diff'))}"> | |||
|
141 | ${_('Raw Diff')} | |||
|
142 | </a> | |||
|
143 | | | |||
|
144 | <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision='?')}" class="tooltip" title="${h.tooltip(_('Patch diff'))}"> | |||
|
145 | ${_('Patch Diff')} | |||
|
146 | </a> | |||
|
147 | | | |||
|
148 | <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision='?',diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}"> | |||
|
149 | ${_('Download Diff')} | |||
|
150 | </a> | |||
|
151 | </div> | |||
|
152 | </div> | |||
|
153 | </div> | |||
|
154 | </%doc> | |||
|
155 | ||||
|
156 | </div> <!-- end summary-detail --> | |||
|
157 | ||||
|
158 | </div> <!-- end summary --> | |||
|
159 | ||||
|
160 | ||||
42 | <div class="table"> |
|
161 | <div class="table"> | |
43 | <div id="codeblock" class="diffblock"> |
|
|||
44 | <div class="code-header" > |
|
|||
45 | <div class="compare_header"> |
|
|||
46 | ## The hidden elements are replaced with a select2 widget |
|
|||
47 | <div class="compare-label">${_('Target')}</div>${h.hidden('compare_source')} |
|
|||
48 | <div class="compare-label">${_('Source')}</div>${h.hidden('compare_target')} |
|
|||
49 |
|
162 | |||
50 | %if not c.preview_mode: |
|
|||
51 | <div class="compare-label"></div> |
|
|||
52 | <div class="compare-buttons"> |
|
|||
53 | %if not c.compare_home: |
|
|||
54 | <a id="btn-swap" class="btn btn-primary" href="${c.swap_url}"><i class="icon-refresh"></i> ${_('Swap')}</a> |
|
|||
55 | %endif |
|
|||
56 | <div id="compare_revs" class="btn btn-primary"><i class ="icon-loop"></i> ${_('Compare Commits')}</div> |
|
|||
57 | %if c.diffset and c.diffset.files: |
|
|||
58 | <div id="compare_changeset_status_toggle" class="btn btn-primary">${_('Comment')}</div> |
|
|||
59 | %endif |
|
|||
60 | </div> |
|
|||
61 | %endif |
|
|||
62 | </div> |
|
|||
63 | </div> |
|
|||
64 | </div> |
|
|||
65 | ## use JS script to load it quickly before potentially large diffs render long time |
|
163 | ## use JS script to load it quickly before potentially large diffs render long time | |
66 | ## this prevents from situation when large diffs block rendering of select2 fields |
|
164 | ## this prevents from situation when large diffs block rendering of select2 fields | |
67 | <script type="text/javascript"> |
|
165 | <script type="text/javascript"> | |
@@ -241,13 +339,26 b'' | |||||
241 |
|
339 | |||
242 | </div> |
|
340 | </div> | |
243 |
|
341 | |||
244 | %if c.compare_home: |
|
342 | %if not c.compare_home: | |
245 | <div id="changeset_compare_view_content"> |
|
343 | <div id="changeset_compare_view_content"> | |
246 | <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div> |
|
344 | <div class="pull-left"> | |
247 |
< |
|
345 | <div class="btn-group"> | |
248 | %else: |
|
346 | <a | |
249 | <div id="changeset_compare_view_content"> |
|
347 | class="btn" | |
250 |
|
|
348 | href="#" | |
|
349 | onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false"> | |||
|
350 | ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)} | |||
|
351 | </a> | |||
|
352 | <a | |||
|
353 | class="btn" | |||
|
354 | href="#" | |||
|
355 | onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false"> | |||
|
356 | ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)} | |||
|
357 | </a> | |||
|
358 | </div> | |||
|
359 | </div> | |||
|
360 | <div style="padding:0 10px 10px 0px" class="pull-left"></div> | |||
|
361 | ## commit compare generated below | |||
251 | <%include file="compare_commits.html"/> |
|
362 | <%include file="compare_commits.html"/> | |
252 | ${cbdiffs.render_diffset_menu()} |
|
363 | ${cbdiffs.render_diffset_menu()} | |
253 | ${cbdiffs.render_diffset(c.diffset)} |
|
364 | ${cbdiffs.render_diffset(c.diffset)} |
@@ -9,11 +9,9 b'' | |||||
9 | </%def> |
|
9 | </%def> | |
10 |
|
10 | |||
11 | <%def name="js_extra()"> |
|
11 | <%def name="js_extra()"> | |
12 | <script type="text/javascript" src="${h.asset('js/mergerly.js', ver=c.rhodecode_version_hash)}"></script> |
|
|||
13 | </%def> |
|
12 | </%def> | |
14 |
|
13 | |||
15 | <%def name="css_extra()"> |
|
14 | <%def name="css_extra()"> | |
16 | <link rel="stylesheet" type="text/css" href="${h.asset('css/mergerly.css', ver=c.rhodecode_version_hash)}"/> |
|
|||
17 | </%def> |
|
15 | </%def> | |
18 |
|
16 | |||
19 |
|
17 |
@@ -128,7 +128,7 b'' | |||||
128 | // used for history, and switch to |
|
128 | // used for history, and switch to | |
129 | var initialCommitData = { |
|
129 | var initialCommitData = { | |
130 | id: null, |
|
130 | id: null, | |
131 |
text: '${_(" |
|
131 | text: '${_("Pick Commit")}', | |
132 | type: 'sha', |
|
132 | type: 'sha', | |
133 | raw_id: null, |
|
133 | raw_id: null, | |
134 | files_url: null |
|
134 | files_url: null | |
@@ -151,9 +151,47 b'' | |||||
151 |
|
151 | |||
152 | // file history select2 |
|
152 | // file history select2 | |
153 | select2FileHistorySwitcher('#diff1', initialCommitData, state); |
|
153 | select2FileHistorySwitcher('#diff1', initialCommitData, state); | |
|
154 | ||||
|
155 | // show at, diff to actions handlers | |||
154 | $('#diff1').on('change', function(e) { |
|
156 | $('#diff1').on('change', function(e) { | |
155 | $('#diff').removeClass('disabled').removeAttr("disabled"); |
|
157 | $('#diff_to_commit').removeClass('disabled').removeAttr("disabled"); | |
156 | $('#show_rev').removeClass('disabled').removeAttr("disabled"); |
|
158 | $('#diff_to_commit').val(_gettext('Diff to Commit ') + e.val.truncateAfter(8, '...')); | |
|
159 | ||||
|
160 | $('#show_at_commit').removeClass('disabled').removeAttr("disabled"); | |||
|
161 | $('#show_at_commit').val(_gettext('Show at Commit ') + e.val.truncateAfter(8, '...')); | |||
|
162 | }); | |||
|
163 | ||||
|
164 | $('#diff_to_commit').on('click', function(e) { | |||
|
165 | var diff1 = $('#diff1').val(); | |||
|
166 | var diff2 = $('#diff2').val(); | |||
|
167 | ||||
|
168 | var url_data = { | |||
|
169 | repo_name: templateContext.repo_name, | |||
|
170 | source_ref: diff1, | |||
|
171 | source_ref_type: 'rev', | |||
|
172 | target_ref: diff2, | |||
|
173 | target_ref_type: 'rev', | |||
|
174 | merge: 1, | |||
|
175 | f_path: state.f_path | |||
|
176 | }; | |||
|
177 | window.location = pyroutes.url('compare_url', url_data); | |||
|
178 | }); | |||
|
179 | ||||
|
180 | $('#show_at_commit').on('click', function(e) { | |||
|
181 | var diff1 = $('#diff1').val(); | |||
|
182 | ||||
|
183 | var annotate = $('#annotate').val(); | |||
|
184 | if (annotate === "True") { | |||
|
185 | var url = pyroutes.url('files_annotate_home', | |||
|
186 | {'repo_name': templateContext.repo_name, | |||
|
187 | 'revision': diff1, 'f_path': state.f_path}); | |||
|
188 | } else { | |||
|
189 | var url = pyroutes.url('files_home', | |||
|
190 | {'repo_name': templateContext.repo_name, | |||
|
191 | 'revision': diff1, 'f_path': state.f_path}); | |||
|
192 | } | |||
|
193 | window.location = url; | |||
|
194 | ||||
157 | }); |
|
195 | }); | |
158 |
|
196 | |||
159 | // show more authors |
|
197 | // show more authors |
@@ -46,17 +46,29 b'' | |||||
46 | </div> |
|
46 | </div> | |
47 |
|
47 | |||
48 |
|
48 | |||
49 |
<div |
|
49 | <div class="fieldset collapsable-content" data-toggle="summary-details"> | |
50 | ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')} |
|
50 | <div class="left-label"> | |
|
51 | ${_('Show/Diff file')}: | |||
|
52 | </div> | |||
|
53 | <div class="right-content"> | |||
51 | ${h.hidden('diff1')} |
|
54 | ${h.hidden('diff1')} | |
52 |
${h.hidden('diff2',c. |
|
55 | ${h.hidden('diff2',c.commit.raw_id)} | |
|
56 | ${h.hidden('annotate', c.annotate)} | |||
|
57 | </div> | |||
|
58 | </div> | |||
53 |
|
59 | |||
54 | ${h.submit('diff',_('Diff to Commit'),class_="btn disabled",disabled="true")} |
|
60 | ||
55 | ${h.submit('show_rev',_('Show at Commit'),class_="btn disabled",disabled="true")} |
|
61 | <div class="fieldset collapsable-content" data-toggle="summary-details"> | |
56 | ${h.hidden('annotate', c.annotate)} |
|
62 | <div class="left-label"> | |
57 | ${h.end_form()} |
|
63 | ${_('Action')}: | |
|
64 | </div> | |||
|
65 | <div class="right-content"> | |||
|
66 | ${h.submit('diff_to_commit',_('Diff to Commit'),class_="btn disabled",disabled="true")} | |||
|
67 | ${h.submit('show_at_commit',_('Show at Commit'),class_="btn disabled",disabled="true")} | |||
|
68 | </div> | |||
58 | </div> |
|
69 | </div> | |
59 |
|
70 | |||
|
71 | ||||
60 | <script> |
|
72 | <script> | |
61 | collapsableContent(); |
|
73 | collapsableContent(); | |
62 | </script> No newline at end of file |
|
74 | </script> |
@@ -360,14 +360,33 b'' | |||||
360 | </div> |
|
360 | </div> | |
361 | % endif |
|
361 | % endif | |
362 | <div class="compare_view_commits_title"> |
|
362 | <div class="compare_view_commits_title"> | |
363 | % if c.allowed_to_update and not c.pull_request.is_closed(): |
|
363 | ||
364 | <a id="update_commits" class="btn btn-primary pull-right">${_('Update commits')}</a> |
|
364 | <div class="pull-left"> | |
365 |
|
|
365 | <div class="btn-group"> | |
366 | <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a> |
|
366 | <a | |
367 |
|
|
367 | class="btn" | |
368 | % if len(c.commit_ranges): |
|
368 | href="#" | |
369 | <h2>${ungettext('Compare View: %s commit','Compare View: %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}</h2> |
|
369 | onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false"> | |
370 | % endif |
|
370 | ${ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)} | |
|
371 | </a> | |||
|
372 | <a | |||
|
373 | class="btn" | |||
|
374 | href="#" | |||
|
375 | onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false"> | |||
|
376 | ${ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)} | |||
|
377 | </a> | |||
|
378 | </div> | |||
|
379 | </div> | |||
|
380 | ||||
|
381 | <div class="pull-right"> | |||
|
382 | % if c.allowed_to_update and not c.pull_request.is_closed(): | |||
|
383 | <a id="update_commits" class="btn btn-primary pull-right">${_('Update commits')}</a> | |||
|
384 | % else: | |||
|
385 | <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a> | |||
|
386 | % endif | |||
|
387 | ||||
|
388 | </div> | |||
|
389 | ||||
371 | </div> |
|
390 | </div> | |
372 | % if not c.missing_commits: |
|
391 | % if not c.missing_commits: | |
373 | <%include file="/compare/compare_commits.html" /> |
|
392 | <%include file="/compare/compare_commits.html" /> |
@@ -244,7 +244,8 b' class TestCommitCommentsController(TestC' | |||||
244 | ('markdown', '# header', '<h1>header</h1>'), |
|
244 | ('markdown', '# header', '<h1>header</h1>'), | |
245 | ('markdown', '*italics*', '<em>italics</em>'), |
|
245 | ('markdown', '*italics*', '<em>italics</em>'), | |
246 | ('markdown', '**bold**', '<strong>bold</strong>'), |
|
246 | ('markdown', '**bold**', '<strong>bold</strong>'), | |
247 | ]) |
|
247 | ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain', | |
|
248 | 'md-header', 'md-italics', 'md-bold', ]) | |||
248 | def test_preview(self, renderer, input, output, backend): |
|
249 | def test_preview(self, renderer, input, output, backend): | |
249 | self.log_user() |
|
250 | self.log_user() | |
250 | params = { |
|
251 | params = { |
@@ -22,16 +22,13 b' import mock' | |||||
22 | import pytest |
|
22 | import pytest | |
23 | import lxml.html |
|
23 | import lxml.html | |
24 |
|
24 | |||
25 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
|||
26 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError |
|
25 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError | |
27 | from rhodecode.model.db import Repository |
|
26 | from rhodecode.tests import url, assert_session_flash | |
28 | from rhodecode.model.scm import ScmModel |
|
27 | from rhodecode.tests.utils import AssertResponse, commit_change | |
29 | from rhodecode.tests import url, TEST_USER_ADMIN_LOGIN, assert_session_flash |
|
|||
30 | from rhodecode.tests.utils import AssertResponse |
|
|||
31 |
|
28 | |||
32 |
|
29 | |||
33 | @pytest.mark.usefixtures("autologin_user", "app") |
|
30 | @pytest.mark.usefixtures("autologin_user", "app") | |
34 | class TestCompareController: |
|
31 | class TestCompareController(object): | |
35 |
|
32 | |||
36 | @pytest.mark.xfail_backends("svn", reason="Requires pull") |
|
33 | @pytest.mark.xfail_backends("svn", reason="Requires pull") | |
37 | def test_compare_remote_with_different_commit_indexes(self, backend): |
|
34 | def test_compare_remote_with_different_commit_indexes(self, backend): | |
@@ -53,23 +50,23 b' class TestCompareController:' | |||||
53 | fork = backend.create_repo() |
|
50 | fork = backend.create_repo() | |
54 |
|
51 | |||
55 | # prepare fork |
|
52 | # prepare fork | |
56 |
commit0 = |
|
53 | commit0 = commit_change( | |
57 | fork.repo_name, filename='file1', content='A', |
|
54 | fork.repo_name, filename='file1', content='A', | |
58 | message='A', vcs_type=backend.alias, parent=None, newfile=True) |
|
55 | message='A', vcs_type=backend.alias, parent=None, newfile=True) | |
59 |
|
56 | |||
60 |
commit1 = |
|
57 | commit1 = commit_change( | |
61 | fork.repo_name, filename='file1', content='B', |
|
58 | fork.repo_name, filename='file1', content='B', | |
62 | message='B, child of A', vcs_type=backend.alias, parent=commit0) |
|
59 | message='B, child of A', vcs_type=backend.alias, parent=commit0) | |
63 |
|
60 | |||
64 |
|
|
61 | commit_change( # commit 2 | |
65 | fork.repo_name, filename='file1', content='C', |
|
62 | fork.repo_name, filename='file1', content='C', | |
66 | message='C, child of B', vcs_type=backend.alias, parent=commit1) |
|
63 | message='C, child of B', vcs_type=backend.alias, parent=commit1) | |
67 |
|
64 | |||
68 |
commit3 = |
|
65 | commit3 = commit_change( | |
69 | fork.repo_name, filename='file1', content='D', |
|
66 | fork.repo_name, filename='file1', content='D', | |
70 | message='D, child of A', vcs_type=backend.alias, parent=commit0) |
|
67 | message='D, child of A', vcs_type=backend.alias, parent=commit0) | |
71 |
|
68 | |||
72 |
commit4 = |
|
69 | commit4 = commit_change( | |
73 | fork.repo_name, filename='file1', content='E', |
|
70 | fork.repo_name, filename='file1', content='E', | |
74 | message='E, child of D', vcs_type=backend.alias, parent=commit3) |
|
71 | message='E, child of D', vcs_type=backend.alias, parent=commit3) | |
75 |
|
72 | |||
@@ -105,7 +102,7 b' class TestCompareController:' | |||||
105 | repo1 = backend.create_repo() |
|
102 | repo1 = backend.create_repo() | |
106 |
|
103 | |||
107 | # commit something ! |
|
104 | # commit something ! | |
108 |
commit0 = |
|
105 | commit0 = commit_change( | |
109 | repo1.repo_name, filename='file1', content='line1\n', |
|
106 | repo1.repo_name, filename='file1', content='line1\n', | |
110 | message='commit1', vcs_type=backend.alias, parent=None, |
|
107 | message='commit1', vcs_type=backend.alias, parent=None, | |
111 | newfile=True) |
|
108 | newfile=True) | |
@@ -114,11 +111,11 b' class TestCompareController:' | |||||
114 | repo2 = backend.create_fork() |
|
111 | repo2 = backend.create_fork() | |
115 |
|
112 | |||
116 | # add two extra commit into fork |
|
113 | # add two extra commit into fork | |
117 |
commit1 = |
|
114 | commit1 = commit_change( | |
118 | repo2.repo_name, filename='file1', content='line1\nline2\n', |
|
115 | repo2.repo_name, filename='file1', content='line1\nline2\n', | |
119 | message='commit2', vcs_type=backend.alias, parent=commit0) |
|
116 | message='commit2', vcs_type=backend.alias, parent=commit0) | |
120 |
|
117 | |||
121 |
commit2 = |
|
118 | commit2 = commit_change( | |
122 | repo2.repo_name, filename='file1', content='line1\nline2\nline3\n', |
|
119 | repo2.repo_name, filename='file1', content='line1\nline2\nline3\n', | |
123 | message='commit3', vcs_type=backend.alias, parent=commit1) |
|
120 | message='commit3', vcs_type=backend.alias, parent=commit1) | |
124 |
|
121 | |||
@@ -156,7 +153,7 b' class TestCompareController:' | |||||
156 | repo1 = backend.create_repo() |
|
153 | repo1 = backend.create_repo() | |
157 |
|
154 | |||
158 | # commit something ! |
|
155 | # commit something ! | |
159 |
commit0 = |
|
156 | commit0 = commit_change( | |
160 | repo1.repo_name, filename='file1', content='line1\n', |
|
157 | repo1.repo_name, filename='file1', content='line1\n', | |
161 | message='commit1', vcs_type=backend.alias, parent=None, |
|
158 | message='commit1', vcs_type=backend.alias, parent=None, | |
162 | newfile=True) |
|
159 | newfile=True) | |
@@ -165,17 +162,17 b' class TestCompareController:' | |||||
165 | repo2 = backend.create_fork() |
|
162 | repo2 = backend.create_fork() | |
166 |
|
163 | |||
167 | # now commit something to origin repo |
|
164 | # now commit something to origin repo | |
168 |
|
|
165 | commit_change( | |
169 | repo1.repo_name, filename='file2', content='line1file2\n', |
|
166 | repo1.repo_name, filename='file2', content='line1file2\n', | |
170 | message='commit2', vcs_type=backend.alias, parent=commit0, |
|
167 | message='commit2', vcs_type=backend.alias, parent=commit0, | |
171 | newfile=True) |
|
168 | newfile=True) | |
172 |
|
169 | |||
173 | # add two extra commit into fork |
|
170 | # add two extra commit into fork | |
174 |
commit1 = |
|
171 | commit1 = commit_change( | |
175 | repo2.repo_name, filename='file1', content='line1\nline2\n', |
|
172 | repo2.repo_name, filename='file1', content='line1\nline2\n', | |
176 | message='commit2', vcs_type=backend.alias, parent=commit0) |
|
173 | message='commit2', vcs_type=backend.alias, parent=commit0) | |
177 |
|
174 | |||
178 |
commit2 = |
|
175 | commit2 = commit_change( | |
179 | repo2.repo_name, filename='file1', content='line1\nline2\nline3\n', |
|
176 | repo2.repo_name, filename='file1', content='line1\nline2\nline3\n', | |
180 | message='commit3', vcs_type=backend.alias, parent=commit1) |
|
177 | message='commit3', vcs_type=backend.alias, parent=commit1) | |
181 |
|
178 | |||
@@ -207,9 +204,9 b' class TestCompareController:' | |||||
207 | compare_page.swap_is_hidden() |
|
204 | compare_page.swap_is_hidden() | |
208 | compare_page.target_source_are_disabled() |
|
205 | compare_page.target_source_are_disabled() | |
209 |
|
206 | |||
210 |
@pytest.mark.xfail_backends("svn" |
|
207 | @pytest.mark.xfail_backends("svn") | |
|
208 | # TODO(marcink): no svn support for compare two seperate repos | |||
211 | def test_compare_of_unrelated_forks(self, backend): |
|
209 | def test_compare_of_unrelated_forks(self, backend): | |
212 | # TODO: johbo: Fails for git due to some other issue it seems |
|
|||
213 | orig = backend.create_repo(number_of_commits=1) |
|
210 | orig = backend.create_repo(number_of_commits=1) | |
214 | fork = backend.create_repo(number_of_commits=1) |
|
211 | fork = backend.create_repo(number_of_commits=1) | |
215 |
|
212 | |||
@@ -245,11 +242,11 b' class TestCompareController:' | |||||
245 | repo1 = backend.create_repo() |
|
242 | repo1 = backend.create_repo() | |
246 |
|
243 | |||
247 | # commit something ! |
|
244 | # commit something ! | |
248 |
commit0 = |
|
245 | commit0 = commit_change( | |
249 | repo1.repo_name, filename='file1', content='line1\n', |
|
246 | repo1.repo_name, filename='file1', content='line1\n', | |
250 | message='commit1', vcs_type=backend.alias, parent=None, |
|
247 | message='commit1', vcs_type=backend.alias, parent=None, | |
251 | newfile=True) |
|
248 | newfile=True) | |
252 |
commit1 = |
|
249 | commit1 = commit_change( | |
253 | repo1.repo_name, filename='file1', content='line1\nline2\n', |
|
250 | repo1.repo_name, filename='file1', content='line1\nline2\n', | |
254 | message='commit2', vcs_type=backend.alias, parent=commit0) |
|
251 | message='commit2', vcs_type=backend.alias, parent=commit0) | |
255 |
|
252 | |||
@@ -257,18 +254,18 b' class TestCompareController:' | |||||
257 | repo2 = backend.create_fork() |
|
254 | repo2 = backend.create_fork() | |
258 |
|
255 | |||
259 | # now make commit3-6 |
|
256 | # now make commit3-6 | |
260 |
commit2 = |
|
257 | commit2 = commit_change( | |
261 | repo1.repo_name, filename='file1', content='line1\nline2\nline3\n', |
|
258 | repo1.repo_name, filename='file1', content='line1\nline2\nline3\n', | |
262 | message='commit3', vcs_type=backend.alias, parent=commit1) |
|
259 | message='commit3', vcs_type=backend.alias, parent=commit1) | |
263 |
commit3 = |
|
260 | commit3 = commit_change( | |
264 | repo1.repo_name, filename='file1', |
|
261 | repo1.repo_name, filename='file1', | |
265 | content='line1\nline2\nline3\nline4\n', message='commit4', |
|
262 | content='line1\nline2\nline3\nline4\n', message='commit4', | |
266 | vcs_type=backend.alias, parent=commit2) |
|
263 | vcs_type=backend.alias, parent=commit2) | |
267 |
commit4 = |
|
264 | commit4 = commit_change( | |
268 | repo1.repo_name, filename='file1', |
|
265 | repo1.repo_name, filename='file1', | |
269 | content='line1\nline2\nline3\nline4\nline5\n', message='commit5', |
|
266 | content='line1\nline2\nline3\nline4\nline5\n', message='commit5', | |
270 | vcs_type=backend.alias, parent=commit3) |
|
267 | vcs_type=backend.alias, parent=commit3) | |
271 |
|
|
268 | commit_change( # commit 5 | |
272 | repo1.repo_name, filename='file1', |
|
269 | repo1.repo_name, filename='file1', | |
273 | content='line1\nline2\nline3\nline4\nline5\nline6\n', |
|
270 | content='line1\nline2\nline3\nline4\nline5\nline6\n', | |
274 | message='commit6', vcs_type=backend.alias, parent=commit4) |
|
271 | message='commit6', vcs_type=backend.alias, parent=commit4) | |
@@ -311,11 +308,11 b' class TestCompareController:' | |||||
311 | repo1 = backend.create_repo() |
|
308 | repo1 = backend.create_repo() | |
312 |
|
309 | |||
313 | # commit something ! |
|
310 | # commit something ! | |
314 |
commit0 = |
|
311 | commit0 = commit_change( | |
315 | repo1.repo_name, filename='file1', content='line1\n', |
|
312 | repo1.repo_name, filename='file1', content='line1\n', | |
316 | message='commit1', vcs_type=backend.alias, parent=None, |
|
313 | message='commit1', vcs_type=backend.alias, parent=None, | |
317 | newfile=True) |
|
314 | newfile=True) | |
318 |
commit1 = |
|
315 | commit1 = commit_change( | |
319 | repo1.repo_name, filename='file1', content='line1\nline2\n', |
|
316 | repo1.repo_name, filename='file1', content='line1\nline2\n', | |
320 | message='commit2', vcs_type=backend.alias, parent=commit0) |
|
317 | message='commit2', vcs_type=backend.alias, parent=commit0) | |
321 |
|
318 | |||
@@ -323,18 +320,18 b' class TestCompareController:' | |||||
323 | backend.create_fork() |
|
320 | backend.create_fork() | |
324 |
|
321 | |||
325 | # now make commit3-6 |
|
322 | # now make commit3-6 | |
326 |
commit2 = |
|
323 | commit2 = commit_change( | |
327 | repo1.repo_name, filename='file1', content='line1\nline2\nline3\n', |
|
324 | repo1.repo_name, filename='file1', content='line1\nline2\nline3\n', | |
328 | message='commit3', vcs_type=backend.alias, parent=commit1) |
|
325 | message='commit3', vcs_type=backend.alias, parent=commit1) | |
329 |
commit3 = |
|
326 | commit3 = commit_change( | |
330 | repo1.repo_name, filename='file1', |
|
327 | repo1.repo_name, filename='file1', | |
331 | content='line1\nline2\nline3\nline4\n', message='commit4', |
|
328 | content='line1\nline2\nline3\nline4\n', message='commit4', | |
332 | vcs_type=backend.alias, parent=commit2) |
|
329 | vcs_type=backend.alias, parent=commit2) | |
333 |
commit4 = |
|
330 | commit4 = commit_change( | |
334 | repo1.repo_name, filename='file1', |
|
331 | repo1.repo_name, filename='file1', | |
335 | content='line1\nline2\nline3\nline4\nline5\n', message='commit5', |
|
332 | content='line1\nline2\nline3\nline4\nline5\n', message='commit5', | |
336 | vcs_type=backend.alias, parent=commit3) |
|
333 | vcs_type=backend.alias, parent=commit3) | |
337 |
commit5 = |
|
334 | commit5 = commit_change( | |
338 | repo1.repo_name, filename='file1', |
|
335 | repo1.repo_name, filename='file1', | |
339 | content='line1\nline2\nline3\nline4\nline5\nline6\n', |
|
336 | content='line1\nline2\nline3\nline4\nline5\nline6\n', | |
340 | message='commit6', vcs_type=backend.alias, parent=commit4) |
|
337 | message='commit6', vcs_type=backend.alias, parent=commit4) | |
@@ -400,7 +397,7 b' class TestCompareController:' | |||||
400 | repo1 = backend.create_repo() |
|
397 | repo1 = backend.create_repo() | |
401 | r1_name = repo1.repo_name |
|
398 | r1_name = repo1.repo_name | |
402 |
|
399 | |||
403 |
commit0 = |
|
400 | commit0 = commit_change( | |
404 | repo=r1_name, filename='file1', |
|
401 | repo=r1_name, filename='file1', | |
405 | content='line1', message='commit1', vcs_type=backend.alias, |
|
402 | content='line1', message='commit1', vcs_type=backend.alias, | |
406 | newfile=True) |
|
403 | newfile=True) | |
@@ -413,19 +410,19 b' class TestCompareController:' | |||||
413 | self.r2_id = repo2.repo_id |
|
410 | self.r2_id = repo2.repo_id | |
414 | r2_name = repo2.repo_name |
|
411 | r2_name = repo2.repo_name | |
415 |
|
412 | |||
416 |
commit1 = |
|
413 | commit1 = commit_change( | |
417 | repo=r2_name, filename='file1-fork', |
|
414 | repo=r2_name, filename='file1-fork', | |
418 | content='file1-line1-from-fork', message='commit1-fork', |
|
415 | content='file1-line1-from-fork', message='commit1-fork', | |
419 | vcs_type=backend.alias, parent=repo2.scm_instance()[-1], |
|
416 | vcs_type=backend.alias, parent=repo2.scm_instance()[-1], | |
420 | newfile=True) |
|
417 | newfile=True) | |
421 |
|
418 | |||
422 |
commit2 = |
|
419 | commit2 = commit_change( | |
423 | repo=r2_name, filename='file2-fork', |
|
420 | repo=r2_name, filename='file2-fork', | |
424 | content='file2-line1-from-fork', message='commit2-fork', |
|
421 | content='file2-line1-from-fork', message='commit2-fork', | |
425 | vcs_type=backend.alias, parent=commit1, |
|
422 | vcs_type=backend.alias, parent=commit1, | |
426 | newfile=True) |
|
423 | newfile=True) | |
427 |
|
424 | |||
428 |
|
|
425 | commit_change( # commit 3 | |
429 | repo=r2_name, filename='file3-fork', |
|
426 | repo=r2_name, filename='file3-fork', | |
430 | content='file3-line1-from-fork', message='commit3-fork', |
|
427 | content='file3-line1-from-fork', message='commit3-fork', | |
431 | vcs_type=backend.alias, parent=commit2, newfile=True) |
|
428 | vcs_type=backend.alias, parent=commit2, newfile=True) | |
@@ -447,9 +444,9 b' class TestCompareController:' | |||||
447 | response.mustcontain('%s@%s' % (r2_name, commit_id1)) |
|
444 | response.mustcontain('%s@%s' % (r2_name, commit_id1)) | |
448 | response.mustcontain('%s@%s' % (r1_name, commit_id2)) |
|
445 | response.mustcontain('%s@%s' % (r1_name, commit_id2)) | |
449 | response.mustcontain('No files') |
|
446 | response.mustcontain('No files') | |
450 |
response.mustcontain('No |
|
447 | response.mustcontain('No commits in this compare') | |
451 |
|
448 | |||
452 |
commit0 = |
|
449 | commit0 = commit_change( | |
453 | repo=r1_name, filename='file2', |
|
450 | repo=r1_name, filename='file2', | |
454 | content='line1-added-after-fork', message='commit2-parent', |
|
451 | content='line1-added-after-fork', message='commit2-parent', | |
455 | vcs_type=backend.alias, parent=None, newfile=True) |
|
452 | vcs_type=backend.alias, parent=None, newfile=True) | |
@@ -558,7 +555,7 b' class TestCompareController:' | |||||
558 |
|
555 | |||
559 |
|
556 | |||
560 | @pytest.mark.usefixtures("autologin_user") |
|
557 | @pytest.mark.usefixtures("autologin_user") | |
561 | class TestCompareControllerSvn: |
|
558 | class TestCompareControllerSvn(object): | |
562 |
|
559 | |||
563 | def test_supports_references_with_path(self, app, backend_svn): |
|
560 | def test_supports_references_with_path(self, app, backend_svn): | |
564 | repo = backend_svn['svn-simple-layout'] |
|
561 | repo = backend_svn['svn-simple-layout'] | |
@@ -574,7 +571,7 b' class TestCompareControllerSvn:' | |||||
574 | status=200) |
|
571 | status=200) | |
575 |
|
572 | |||
576 | # Expecting no commits, since both paths are at the same revision |
|
573 | # Expecting no commits, since both paths are at the same revision | |
577 |
response.mustcontain('No |
|
574 | response.mustcontain('No commits in this compare') | |
578 |
|
575 | |||
579 | # Should find only one file changed when comparing those two tags |
|
576 | # Should find only one file changed when comparing those two tags | |
580 | response.mustcontain('example.py') |
|
577 | response.mustcontain('example.py') | |
@@ -596,7 +593,7 b' class TestCompareControllerSvn:' | |||||
596 | status=200) |
|
593 | status=200) | |
597 |
|
594 | |||
598 | # It should show commits |
|
595 | # It should show commits | |
599 |
assert 'No |
|
596 | assert 'No commits in this compare' not in response.body | |
600 |
|
597 | |||
601 | # Should find only one file changed when comparing those two tags |
|
598 | # Should find only one file changed when comparing those two tags | |
602 | response.mustcontain('example.py') |
|
599 | response.mustcontain('example.py') | |
@@ -660,36 +657,3 b' class ComparePage(AssertResponse):' | |||||
660 | def target_source_are_enabled(self): |
|
657 | def target_source_are_enabled(self): | |
661 | response = self.response |
|
658 | response = self.response | |
662 | response.mustcontain("var enable_fields = true;") |
|
659 | response.mustcontain("var enable_fields = true;") | |
663 |
|
||||
664 |
|
||||
665 | def _commit_change( |
|
|||
666 | repo, filename, content, message, vcs_type, parent=None, |
|
|||
667 | newfile=False): |
|
|||
668 | repo = Repository.get_by_repo_name(repo) |
|
|||
669 | _commit = parent |
|
|||
670 | if not parent: |
|
|||
671 | _commit = EmptyCommit(alias=vcs_type) |
|
|||
672 |
|
||||
673 | if newfile: |
|
|||
674 | nodes = { |
|
|||
675 | filename: { |
|
|||
676 | 'content': content |
|
|||
677 | } |
|
|||
678 | } |
|
|||
679 | commit = ScmModel().create_nodes( |
|
|||
680 | user=TEST_USER_ADMIN_LOGIN, repo=repo, |
|
|||
681 | message=message, |
|
|||
682 | nodes=nodes, |
|
|||
683 | parent_commit=_commit, |
|
|||
684 | author=TEST_USER_ADMIN_LOGIN, |
|
|||
685 | ) |
|
|||
686 | else: |
|
|||
687 | commit = ScmModel().commit_change( |
|
|||
688 | repo=repo.scm_instance(), repo_name=repo.repo_name, |
|
|||
689 | commit=parent, user=TEST_USER_ADMIN_LOGIN, |
|
|||
690 | author=TEST_USER_ADMIN_LOGIN, |
|
|||
691 | message=message, |
|
|||
692 | content=content, |
|
|||
693 | f_path=filename |
|
|||
694 | ) |
|
|||
695 | return commit |
|
@@ -44,7 +44,7 b' class TestCompareController:' | |||||
44 | response.mustcontain('%s@%s' % (backend.repo_name, tag1)) |
|
44 | response.mustcontain('%s@%s' % (backend.repo_name, tag1)) | |
45 | response.mustcontain('%s@%s' % (backend.repo_name, tag2)) |
|
45 | response.mustcontain('%s@%s' % (backend.repo_name, tag2)) | |
46 |
|
46 | |||
47 |
# outgoing c |
|
47 | # outgoing commits between tags | |
48 | commit_indexes = { |
|
48 | commit_indexes = { | |
49 | 'git': [113] + range(115, 121), |
|
49 | 'git': [113] + range(115, 121), | |
50 | 'hg': [112] + range(115, 121), |
|
50 | 'hg': [112] + range(115, 121), | |
@@ -118,8 +118,8 b' class TestCompareController:' | |||||
118 | response.mustcontain('%s@%s' % (backend.repo_name, head_id)) |
|
118 | response.mustcontain('%s@%s' % (backend.repo_name, head_id)) | |
119 |
|
119 | |||
120 | # branches are equal |
|
120 | # branches are equal | |
121 |
response.mustcontain(' |
|
121 | response.mustcontain('No files') | |
122 |
response.mustcontain(' |
|
122 | response.mustcontain('No commits in this compare') | |
123 |
|
123 | |||
124 | def test_compare_commits(self, backend): |
|
124 | def test_compare_commits(self, backend): | |
125 | repo = backend.repo |
|
125 | repo = backend.repo |
@@ -28,15 +28,11 b' from rhodecode.lib import helpers as h' | |||||
28 | from rhodecode.lib.compat import OrderedDict |
|
28 | from rhodecode.lib.compat import OrderedDict | |
29 | from rhodecode.lib.ext_json import json |
|
29 | from rhodecode.lib.ext_json import json | |
30 | from rhodecode.lib.vcs import nodes |
|
30 | from rhodecode.lib.vcs import nodes | |
31 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
31 | ||
32 | from rhodecode.lib.vcs.conf import settings |
|
32 | from rhodecode.lib.vcs.conf import settings | |
33 | from rhodecode.lib.vcs.nodes import FileNode |
|
|||
34 | from rhodecode.model.db import Repository |
|
|||
35 | from rhodecode.model.scm import ScmModel |
|
|||
36 | from rhodecode.tests import ( |
|
33 | from rhodecode.tests import ( | |
37 |
url |
|
34 | url, assert_session_flash, assert_not_in_session_flash) | |
38 | from rhodecode.tests.fixture import Fixture |
|
35 | from rhodecode.tests.fixture import Fixture | |
39 | from rhodecode.tests.utils import AssertResponse |
|
|||
40 |
|
36 | |||
41 | fixture = Fixture() |
|
37 | fixture = Fixture() | |
42 |
|
38 | |||
@@ -48,40 +44,6 b' NODE_HISTORY = {' | |||||
48 |
|
44 | |||
49 |
|
45 | |||
50 |
|
46 | |||
51 | def _commit_change( |
|
|||
52 | repo, filename, content, message, vcs_type, parent=None, |
|
|||
53 | newfile=False): |
|
|||
54 | repo = Repository.get_by_repo_name(repo) |
|
|||
55 | _commit = parent |
|
|||
56 | if not parent: |
|
|||
57 | _commit = EmptyCommit(alias=vcs_type) |
|
|||
58 |
|
||||
59 | if newfile: |
|
|||
60 | nodes = { |
|
|||
61 | filename: { |
|
|||
62 | 'content': content |
|
|||
63 | } |
|
|||
64 | } |
|
|||
65 | commit = ScmModel().create_nodes( |
|
|||
66 | user=TEST_USER_ADMIN_LOGIN, repo=repo, |
|
|||
67 | message=message, |
|
|||
68 | nodes=nodes, |
|
|||
69 | parent_commit=_commit, |
|
|||
70 | author=TEST_USER_ADMIN_LOGIN, |
|
|||
71 | ) |
|
|||
72 | else: |
|
|||
73 | commit = ScmModel().commit_change( |
|
|||
74 | repo=repo.scm_instance(), repo_name=repo.repo_name, |
|
|||
75 | commit=parent, user=TEST_USER_ADMIN_LOGIN, |
|
|||
76 | author=TEST_USER_ADMIN_LOGIN, |
|
|||
77 | message=message, |
|
|||
78 | content=content, |
|
|||
79 | f_path=filename |
|
|||
80 | ) |
|
|||
81 | return commit |
|
|||
82 |
|
||||
83 |
|
||||
84 |
|
||||
85 | @pytest.mark.usefixtures("app") |
|
47 | @pytest.mark.usefixtures("app") | |
86 | class TestFilesController: |
|
48 | class TestFilesController: | |
87 |
|
49 | |||
@@ -120,7 +82,7 b' class TestFilesController:' | |||||
120 | response = self.app.get(url( |
|
82 | response = self.app.get(url( | |
121 | controller='files', action='index', |
|
83 | controller='files', action='index', | |
122 | repo_name=repo.repo_name, revision='tip', f_path='/')) |
|
84 | repo_name=repo.repo_name, revision='tip', f_path='/')) | |
123 |
assert_response = |
|
85 | assert_response = response.assert_response() | |
124 | assert_response.contains_one_link( |
|
86 | assert_response.contains_one_link( | |
125 | 'absolute-path @ 000000000000', 'http://example.com/absolute-path') |
|
87 | 'absolute-path @ 000000000000', 'http://example.com/absolute-path') | |
126 |
|
88 | |||
@@ -130,7 +92,7 b' class TestFilesController:' | |||||
130 | response = self.app.get(url( |
|
92 | response = self.app.get(url( | |
131 | controller='files', action='index', |
|
93 | controller='files', action='index', | |
132 | repo_name=repo.repo_name, revision='tip', f_path='/')) |
|
94 | repo_name=repo.repo_name, revision='tip', f_path='/')) | |
133 |
assert_response = |
|
95 | assert_response = response.assert_response() | |
134 | assert_response.contains_one_link( |
|
96 | assert_response.contains_one_link( | |
135 | 'subpaths-path @ 000000000000', |
|
97 | 'subpaths-path @ 000000000000', | |
136 | 'http://sub-base.example.com/subpaths-path') |
|
98 | 'http://sub-base.example.com/subpaths-path') | |
@@ -179,21 +141,24 b' class TestFilesController:' | |||||
179 | assert_dirs_in_response(response, dirs, params) |
|
141 | assert_dirs_in_response(response, dirs, params) | |
180 | assert_files_in_response(response, files, params) |
|
142 | assert_files_in_response(response, files, params) | |
181 |
|
143 | |||
182 | @pytest.mark.xfail_backends("git", reason="Missing branches in git repo") |
|
|||
183 | @pytest.mark.xfail_backends("svn", reason="Depends on branch support") |
|
|||
184 | def test_index_different_branch(self, backend): |
|
144 | def test_index_different_branch(self, backend): | |
185 | # TODO: Git test repository does not contain branches |
|
145 | branches = dict( | |
186 | # TODO: Branch support in Subversion |
|
146 | hg=(150, ['git']), | |
187 |
|
147 | # TODO: Git test repository does not contain other branches | ||
188 | commit = backend.repo.get_commit(commit_idx=150) |
|
148 | git=(633, ['master']), | |
|
149 | # TODO: Branch support in Subversion | |||
|
150 | svn=(150, []) | |||
|
151 | ) | |||
|
152 | idx, branches = branches[backend.alias] | |||
|
153 | commit = backend.repo.get_commit(commit_idx=idx) | |||
189 | response = self.app.get(url( |
|
154 | response = self.app.get(url( | |
190 | controller='files', action='index', |
|
155 | controller='files', action='index', | |
191 | repo_name=backend.repo_name, |
|
156 | repo_name=backend.repo_name, | |
192 | revision=commit.raw_id, |
|
157 | revision=commit.raw_id, | |
193 | f_path='/')) |
|
158 | f_path='/')) | |
194 |
assert_response = |
|
159 | assert_response = response.assert_response() | |
195 | assert_response.element_contains( |
|
160 | for branch in branches: | |
196 |
'.tags .branchtag', |
|
161 | assert_response.element_contains('.tags .branchtag', branch) | |
197 |
|
162 | |||
198 | def test_index_paging(self, backend): |
|
163 | def test_index_paging(self, backend): | |
199 | repo = backend.repo |
|
164 | repo = backend.repo | |
@@ -221,7 +186,7 b' class TestFilesController:' | |||||
221 | msgbox = """<div class="commit right-content">%s</div>""" |
|
186 | msgbox = """<div class="commit right-content">%s</div>""" | |
222 | response.mustcontain(msgbox % (commit.message, )) |
|
187 | response.mustcontain(msgbox % (commit.message, )) | |
223 |
|
188 | |||
224 |
assert_response = |
|
189 | assert_response = response.assert_response() | |
225 | if commit.branch: |
|
190 | if commit.branch: | |
226 | assert_response.element_contains('.tags.tags-main .branchtag', commit.branch) |
|
191 | assert_response.element_contains('.tags.tags-main .branchtag', commit.branch) | |
227 | if commit.tags: |
|
192 | if commit.tags: | |
@@ -348,7 +313,7 b' class TestFilesController:' | |||||
348 | f_path='/', commit_id=commit.raw_id), |
|
313 | f_path='/', commit_id=commit.raw_id), | |
349 | extra_environ=xhr_header) |
|
314 | extra_environ=xhr_header) | |
350 |
|
315 | |||
351 |
assert_response = |
|
316 | assert_response = response.assert_response() | |
352 |
|
317 | |||
353 | for attr in ['data-commit-id', 'data-date', 'data-author']: |
|
318 | for attr in ['data-commit-id', 'data-date', 'data-author']: | |
354 | elements = assert_response.get_elements('[{}]'.format(attr)) |
|
319 | elements = assert_response.get_elements('[{}]'.format(attr)) | |
@@ -401,7 +366,7 b' class TestFilesController:' | |||||
401 | # TODO: johbo: Think about a better place for these tests. Either controller |
|
366 | # TODO: johbo: Think about a better place for these tests. Either controller | |
402 | # specific unit tests or we move down the whole logic further towards the vcs |
|
367 | # specific unit tests or we move down the whole logic further towards the vcs | |
403 | # layer |
|
368 | # layer | |
404 | class TestAdjustFilePathForSvn: |
|
369 | class TestAdjustFilePathForSvn(object): | |
405 | """SVN specific adjustments of node history in FileController.""" |
|
370 | """SVN specific adjustments of node history in FileController.""" | |
406 |
|
371 | |||
407 | def test_returns_path_relative_to_matched_reference(self): |
|
372 | def test_returns_path_relative_to_matched_reference(self): | |
@@ -433,7 +398,7 b' class TestAdjustFilePathForSvn:' | |||||
433 |
|
398 | |||
434 |
|
399 | |||
435 | @pytest.mark.usefixtures("app") |
|
400 | @pytest.mark.usefixtures("app") | |
436 | class TestRepositoryArchival: |
|
401 | class TestRepositoryArchival(object): | |
437 |
|
402 | |||
438 | def test_archival(self, backend): |
|
403 | def test_archival(self, backend): | |
439 | backend.enable_downloads() |
|
404 | backend.enable_downloads() | |
@@ -485,7 +450,7 b' class TestRepositoryArchival:' | |||||
485 |
|
450 | |||
486 |
|
451 | |||
487 | @pytest.mark.usefixtures("app", "autologin_user") |
|
452 | @pytest.mark.usefixtures("app", "autologin_user") | |
488 | class TestRawFileHandling: |
|
453 | class TestRawFileHandling(object): | |
489 |
|
454 | |||
490 | def test_raw_file_ok(self, backend): |
|
455 | def test_raw_file_ok(self, backend): | |
491 | commit = backend.repo.get_commit(commit_idx=173) |
|
456 | commit = backend.repo.get_commit(commit_idx=173) | |
@@ -575,6 +540,7 b' class TestFilesDiff:' | |||||
575 | def test_file_full_diff(self, backend, diff): |
|
540 | def test_file_full_diff(self, backend, diff): | |
576 | commit1 = backend.repo.get_commit(commit_idx=-1) |
|
541 | commit1 = backend.repo.get_commit(commit_idx=-1) | |
577 | commit2 = backend.repo.get_commit(commit_idx=-2) |
|
542 | commit2 = backend.repo.get_commit(commit_idx=-2) | |
|
543 | ||||
578 | response = self.app.get( |
|
544 | response = self.app.get( | |
579 | url( |
|
545 | url( | |
580 | controller='files', |
|
546 | controller='files', | |
@@ -582,11 +548,17 b' class TestFilesDiff:' | |||||
582 | repo_name=backend.repo_name, |
|
548 | repo_name=backend.repo_name, | |
583 | f_path='README'), |
|
549 | f_path='README'), | |
584 | params={ |
|
550 | params={ | |
585 |
'diff1': commit |
|
551 | 'diff1': commit2.raw_id, | |
586 |
'diff2': commit |
|
552 | 'diff2': commit1.raw_id, | |
587 | 'fulldiff': '1', |
|
553 | 'fulldiff': '1', | |
588 | 'diff': diff, |
|
554 | 'diff': diff, | |
589 | }) |
|
555 | }) | |
|
556 | ||||
|
557 | if diff == 'diff': | |||
|
558 | # use redirect since this is OLD view redirecting to compare page | |||
|
559 | response = response.follow() | |||
|
560 | ||||
|
561 | # It's a symlink to README.rst | |||
590 | response.mustcontain('README.rst') |
|
562 | response.mustcontain('README.rst') | |
591 | response.mustcontain('No newline at end of file') |
|
563 | response.mustcontain('No newline at end of file') | |
592 |
|
564 | |||
@@ -610,7 +582,17 b' class TestFilesDiff:' | |||||
610 | 'fulldiff': '1', |
|
582 | 'fulldiff': '1', | |
611 | 'diff': 'diff', |
|
583 | 'diff': 'diff', | |
612 | }) |
|
584 | }) | |
613 | response.mustcontain('Cannot diff binary files') |
|
585 | # use redirect since this is OLD view redirecting to compare page | |
|
586 | response = response.follow() | |||
|
587 | response.mustcontain('Expand 1 commit') | |||
|
588 | response.mustcontain('1 file changed: 0 inserted, 0 deleted') | |||
|
589 | ||||
|
590 | if backend.alias == 'svn': | |||
|
591 | response.mustcontain('new file 10644') | |||
|
592 | # TODO(marcink): SVN doesn't yet detect binary changes | |||
|
593 | else: | |||
|
594 | response.mustcontain('new file 100644') | |||
|
595 | response.mustcontain('binary diff hidden') | |||
614 |
|
596 | |||
615 | def test_diff_2way(self, backend): |
|
597 | def test_diff_2way(self, backend): | |
616 | commit1 = backend.repo.get_commit(commit_idx=-1) |
|
598 | commit1 = backend.repo.get_commit(commit_idx=-1) | |
@@ -622,14 +604,15 b' class TestFilesDiff:' | |||||
622 | repo_name=backend.repo_name, |
|
604 | repo_name=backend.repo_name, | |
623 | f_path='README'), |
|
605 | f_path='README'), | |
624 | params={ |
|
606 | params={ | |
625 |
'diff1': commit |
|
607 | 'diff1': commit2.raw_id, | |
626 |
'diff2': commit |
|
608 | 'diff2': commit1.raw_id, | |
627 | }) |
|
609 | }) | |
|
610 | # use redirect since this is OLD view redirecting to compare page | |||
|
611 | response = response.follow() | |||
628 |
|
612 | |||
629 | # Expecting links to both variants of the file. Links are used |
|
613 | # It's a symlink to README.rst | |
630 | # to load the content dynamically. |
|
614 | response.mustcontain('README.rst') | |
631 | response.mustcontain('/%s/README' % commit1.raw_id) |
|
615 | response.mustcontain('No newline at end of file') | |
632 | response.mustcontain('/%s/README' % commit2.raw_id) |
|
|||
633 |
|
616 | |||
634 | def test_requires_one_commit_id(self, backend, autologin_user): |
|
617 | def test_requires_one_commit_id(self, backend, autologin_user): | |
635 | response = self.app.get( |
|
618 | response = self.app.get( | |
@@ -642,21 +625,23 b' class TestFilesDiff:' | |||||
642 | response.mustcontain( |
|
625 | response.mustcontain( | |
643 | 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.') |
|
626 | 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.') | |
644 |
|
627 | |||
645 |
def test_returns_no |
|
628 | def test_returns_no_files_if_file_does_not_exist(self, vcsbackend): | |
646 | repo = vcsbackend.repo |
|
629 | repo = vcsbackend.repo | |
647 | self.app.get( |
|
630 | response = self.app.get( | |
648 | url( |
|
631 | url( | |
649 | controller='files', |
|
632 | controller='files', | |
650 | action='diff', |
|
633 | action='diff', | |
651 | repo_name=repo.name, |
|
634 | repo_name=repo.name, | |
652 | f_path='does-not-exist-in-any-commit', |
|
635 | f_path='does-not-exist-in-any-commit', | |
653 | diff1=repo[0].raw_id, |
|
636 | diff1=repo[0].raw_id, | |
654 | diff2=repo[1].raw_id), |
|
637 | diff2=repo[1].raw_id),) | |
655 | status=404) |
|
638 | ||
|
639 | response = response.follow() | |||
|
640 | response.mustcontain('No files') | |||
656 |
|
641 | |||
657 | def test_returns_redirect_if_file_not_changed(self, backend): |
|
642 | def test_returns_redirect_if_file_not_changed(self, backend): | |
658 | commit = backend.repo.get_commit(commit_idx=-1) |
|
643 | commit = backend.repo.get_commit(commit_idx=-1) | |
659 | f_path= 'README' |
|
644 | f_path = 'README' | |
660 | response = self.app.get( |
|
645 | response = self.app.get( | |
661 | url( |
|
646 | url( | |
662 | controller='files', |
|
647 | controller='files', | |
@@ -666,25 +651,40 b' class TestFilesDiff:' | |||||
666 | diff1=commit.raw_id, |
|
651 | diff1=commit.raw_id, | |
667 | diff2=commit.raw_id, |
|
652 | diff2=commit.raw_id, | |
668 | ), |
|
653 | ), | |
669 | status=302 |
|
|||
670 | ) |
|
654 | ) | |
671 | assert response.headers['Location'].endswith(f_path) |
|
655 | response = response.follow() | |
672 | redirected = response.follow() |
|
656 | response.mustcontain('No files') | |
673 |
re |
|
657 | response.mustcontain('No commits in this compare') | |
674 |
|
658 | |||
675 | def test_supports_diff_to_different_path_svn(self, backend_svn): |
|
659 | def test_supports_diff_to_different_path_svn(self, backend_svn): | |
|
660 | #TODO: check this case | |||
|
661 | return | |||
|
662 | ||||
676 | repo = backend_svn['svn-simple-layout'].scm_instance() |
|
663 | repo = backend_svn['svn-simple-layout'].scm_instance() | |
677 |
commit_id = |
|
664 | commit_id_1 = '24' | |
|
665 | commit_id_2 = '26' | |||
|
666 | ||||
|
667 | ||||
|
668 | print( url( | |||
|
669 | controller='files', | |||
|
670 | action='diff', | |||
|
671 | repo_name=repo.name, | |||
|
672 | f_path='trunk/example.py', | |||
|
673 | diff1='tags/v0.2/example.py@' + commit_id_1, | |||
|
674 | diff2=commit_id_2)) | |||
|
675 | ||||
678 | response = self.app.get( |
|
676 | response = self.app.get( | |
679 | url( |
|
677 | url( | |
680 | controller='files', |
|
678 | controller='files', | |
681 | action='diff', |
|
679 | action='diff', | |
682 | repo_name=repo.name, |
|
680 | repo_name=repo.name, | |
683 | f_path='trunk/example.py', |
|
681 | f_path='trunk/example.py', | |
684 | diff1='tags/v0.2/example.py@' + commit_id, |
|
682 | diff1='tags/v0.2/example.py@' + commit_id_1, | |
685 |
diff2=commit_id) |
|
683 | diff2=commit_id_2)) | |
686 | status=200) |
|
684 | ||
|
685 | response = response.follow() | |||
687 | response.mustcontain( |
|
686 | response.mustcontain( | |
|
687 | # diff contains this | |||
688 | "Will print out a useful message on invocation.") |
|
688 | "Will print out a useful message on invocation.") | |
689 |
|
689 | |||
690 | # Note: Expecting that we indicate the user what's being compared |
|
690 | # Note: Expecting that we indicate the user what's being compared | |
@@ -692,6 +692,9 b' class TestFilesDiff:' | |||||
692 | response.mustcontain("tags/v0.2/example.py") |
|
692 | response.mustcontain("tags/v0.2/example.py") | |
693 |
|
693 | |||
694 | def test_show_rev_redirects_to_svn_path(self, backend_svn): |
|
694 | def test_show_rev_redirects_to_svn_path(self, backend_svn): | |
|
695 | #TODO: check this case | |||
|
696 | return | |||
|
697 | ||||
695 | repo = backend_svn['svn-simple-layout'].scm_instance() |
|
698 | repo = backend_svn['svn-simple-layout'].scm_instance() | |
696 | commit_id = repo[-1].raw_id |
|
699 | commit_id = repo[-1].raw_id | |
697 | response = self.app.get( |
|
700 | response = self.app.get( | |
@@ -708,6 +711,9 b' class TestFilesDiff:' | |||||
708 | 'svn-svn-simple-layout/files/26/branches/argparse/example.py') |
|
711 | 'svn-svn-simple-layout/files/26/branches/argparse/example.py') | |
709 |
|
712 | |||
710 | def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn): |
|
713 | def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn): | |
|
714 | #TODO: check this case | |||
|
715 | return | |||
|
716 | ||||
711 | repo = backend_svn['svn-simple-layout'].scm_instance() |
|
717 | repo = backend_svn['svn-simple-layout'].scm_instance() | |
712 | commit_id = repo[-1].raw_id |
|
718 | commit_id = repo[-1].raw_id | |
713 | response = self.app.get( |
|
719 | response = self.app.get( | |
@@ -979,100 +985,3 b' def _assert_items_in_response(response, ' | |||||
979 | def assert_timeago_in_response(response, items, params): |
|
985 | def assert_timeago_in_response(response, items, params): | |
980 | for item in items: |
|
986 | for item in items: | |
981 | response.mustcontain(h.age_component(params['date'])) |
|
987 | response.mustcontain(h.age_component(params['date'])) | |
982 |
|
||||
983 |
|
||||
984 |
|
||||
985 | @pytest.mark.usefixtures("autologin_user", "app") |
|
|||
986 | class TestSideBySideDiff: |
|
|||
987 |
|
||||
988 | def test_diff2way(self, app, backend, backend_stub): |
|
|||
989 | f_path = 'content' |
|
|||
990 | commit1_content = 'content-25d7e49c18b159446c' |
|
|||
991 | commit2_content = 'content-603d6c72c46d953420' |
|
|||
992 | repo = backend.create_repo() |
|
|||
993 |
|
||||
994 | commit1 = _commit_change( |
|
|||
995 | repo.repo_name, filename=f_path, content=commit1_content, |
|
|||
996 | message='A', vcs_type=backend.alias, parent=None, newfile=True) |
|
|||
997 |
|
||||
998 | commit2 = _commit_change( |
|
|||
999 | repo.repo_name, filename=f_path, content=commit2_content, |
|
|||
1000 | message='B, child of A', vcs_type=backend.alias, parent=commit1) |
|
|||
1001 |
|
||||
1002 | response = self.app.get(url( |
|
|||
1003 | controller='files', action='diff_2way', |
|
|||
1004 | repo_name=repo.repo_name, |
|
|||
1005 | diff1=commit1.raw_id, |
|
|||
1006 | diff2=commit2.raw_id, |
|
|||
1007 | f_path=f_path)) |
|
|||
1008 |
|
||||
1009 | assert_response = AssertResponse(response) |
|
|||
1010 | response.mustcontain( |
|
|||
1011 | ('Side-by-side Diff r0:%s ... r1:%s') % ( commit1.short_id, commit2.short_id )) |
|
|||
1012 | response.mustcontain('id="compare"') |
|
|||
1013 | response.mustcontain(( |
|
|||
1014 | "var orig1_url = '/%s/raw/%s/%s';\n" |
|
|||
1015 | "var orig2_url = '/%s/raw/%s/%s';") % |
|
|||
1016 | ( repo.repo_name, commit1.raw_id, f_path, |
|
|||
1017 | repo.repo_name, commit2.raw_id, f_path)) |
|
|||
1018 |
|
||||
1019 |
|
||||
1020 | def test_diff2way_with_empty_file(self, app, backend, backend_stub): |
|
|||
1021 | commits = [ |
|
|||
1022 | {'message': 'First commit'}, |
|
|||
1023 | {'message': 'Commit with binary', |
|
|||
1024 | 'added': [nodes.FileNode('file.empty', content='')]}, |
|
|||
1025 | ] |
|
|||
1026 | f_path='file.empty' |
|
|||
1027 | repo = backend.create_repo(commits=commits) |
|
|||
1028 | commit_id1 = repo.get_commit(commit_idx=0).raw_id |
|
|||
1029 | commit_id2 = repo.get_commit(commit_idx=1).raw_id |
|
|||
1030 |
|
||||
1031 | response = self.app.get(url( |
|
|||
1032 | controller='files', action='diff_2way', |
|
|||
1033 | repo_name=repo.repo_name, |
|
|||
1034 | diff1=commit_id1, |
|
|||
1035 | diff2=commit_id2, |
|
|||
1036 | f_path=f_path)) |
|
|||
1037 |
|
||||
1038 | assert_response = AssertResponse(response) |
|
|||
1039 | if backend.alias == 'svn': |
|
|||
1040 | assert_session_flash( response, |
|
|||
1041 | ('%(file_path)s has not changed') % { 'file_path': 'file.empty' }) |
|
|||
1042 | else: |
|
|||
1043 | response.mustcontain( |
|
|||
1044 | ('Side-by-side Diff r0:%s ... r1:%s') % ( repo.get_commit(commit_idx=0).short_id, repo.get_commit(commit_idx=1).short_id )) |
|
|||
1045 | response.mustcontain('id="compare"') |
|
|||
1046 | response.mustcontain(( |
|
|||
1047 | "var orig1_url = '/%s/raw/%s/%s';\n" |
|
|||
1048 | "var orig2_url = '/%s/raw/%s/%s';") % |
|
|||
1049 | ( repo.repo_name, commit_id1, f_path, |
|
|||
1050 | repo.repo_name, commit_id2, f_path)) |
|
|||
1051 |
|
||||
1052 |
|
||||
1053 | def test_empty_diff_2way_redirect_to_summary_with_alert(self, app, backend): |
|
|||
1054 | commit_id_range = { |
|
|||
1055 | 'hg': ( |
|
|||
1056 | '25d7e49c18b159446cadfa506a5cf8ad1cb04067', |
|
|||
1057 | '603d6c72c46d953420c89d36372f08d9f305f5dd'), |
|
|||
1058 | 'git': ( |
|
|||
1059 | '6fc9270775aaf5544c1deb014f4ddd60c952fcbb', |
|
|||
1060 | '03fa803d7e9fb14daa9a3089e0d1494eda75d986'), |
|
|||
1061 | 'svn': ( |
|
|||
1062 | '335', |
|
|||
1063 | '337'), |
|
|||
1064 | } |
|
|||
1065 | f_path = 'setup.py' |
|
|||
1066 |
|
||||
1067 | commit_ids = commit_id_range[backend.alias] |
|
|||
1068 |
|
||||
1069 | response = self.app.get(url( |
|
|||
1070 | controller='files', action='diff_2way', |
|
|||
1071 | repo_name=backend.repo_name, |
|
|||
1072 | diff2=commit_ids[0], |
|
|||
1073 | diff1=commit_ids[1], |
|
|||
1074 | f_path=f_path)) |
|
|||
1075 |
|
||||
1076 | assert_response = AssertResponse(response) |
|
|||
1077 | assert_session_flash( response, |
|
|||
1078 | ('%(file_path)s has not changed') % { 'file_path': f_path }) |
|
@@ -531,6 +531,81 b' DIFF_FIXTURES = [' | |||||
531 | }), |
|
531 | }), | |
532 | ]), |
|
532 | ]), | |
533 |
|
533 | |||
|
534 | ('svn', | |||
|
535 | 'svn_diff_binary_add_file.diff', | |||
|
536 | [('intl.dll', 'A', | |||
|
537 | {'added': 0, | |||
|
538 | 'deleted': 0, | |||
|
539 | 'binary': False, | |||
|
540 | 'ops': {NEW_FILENODE: 'new file 10644', | |||
|
541 | #TODO(Marcink): depends on binary detection on svn patches | |||
|
542 | # BIN_FILENODE: 'binary diff hidden' | |||
|
543 | } | |||
|
544 | }), | |||
|
545 | ]), | |||
|
546 | ||||
|
547 | ('svn', | |||
|
548 | 'svn_diff_multiple_changes.diff', | |||
|
549 | [('trunk/doc/images/SettingsOverlay.png', 'M', | |||
|
550 | {'added': 0, | |||
|
551 | 'deleted': 0, | |||
|
552 | 'binary': False, | |||
|
553 | 'ops': {MOD_FILENODE: 'modified file', | |||
|
554 | #TODO(Marcink): depends on binary detection on svn patches | |||
|
555 | # BIN_FILENODE: 'binary diff hidden' | |||
|
556 | } | |||
|
557 | }), | |||
|
558 | ('trunk/doc/source/de/tsvn_ch04.xml', 'M', | |||
|
559 | {'added': 89, | |||
|
560 | 'deleted': 34, | |||
|
561 | 'binary': False, | |||
|
562 | 'ops': {MOD_FILENODE: 'modified file'} | |||
|
563 | }), | |||
|
564 | ('trunk/doc/source/en/tsvn_ch04.xml', 'M', | |||
|
565 | {'added': 66, | |||
|
566 | 'deleted': 21, | |||
|
567 | 'binary': False, | |||
|
568 | 'ops': {MOD_FILENODE: 'modified file'} | |||
|
569 | }), | |||
|
570 | ('trunk/src/Changelog.txt', 'M', | |||
|
571 | {'added': 2, | |||
|
572 | 'deleted': 0, | |||
|
573 | 'binary': False, | |||
|
574 | 'ops': {MOD_FILENODE: 'modified file'} | |||
|
575 | }), | |||
|
576 | ('trunk/src/Resources/TortoiseProcENG.rc', 'M', | |||
|
577 | {'added': 19, | |||
|
578 | 'deleted': 13, | |||
|
579 | 'binary': False, | |||
|
580 | 'ops': {MOD_FILENODE: 'modified file'} | |||
|
581 | }), | |||
|
582 | ('trunk/src/TortoiseProc/SetOverlayPage.cpp', 'M', | |||
|
583 | {'added': 16, | |||
|
584 | 'deleted': 1, | |||
|
585 | 'binary': False, | |||
|
586 | 'ops': {MOD_FILENODE: 'modified file'} | |||
|
587 | }), | |||
|
588 | ('trunk/src/TortoiseProc/SetOverlayPage.h', 'M', | |||
|
589 | {'added': 3, | |||
|
590 | 'deleted': 0, | |||
|
591 | 'binary': False, | |||
|
592 | 'ops': {MOD_FILENODE: 'modified file'} | |||
|
593 | }), | |||
|
594 | ('trunk/src/TortoiseProc/resource.h', 'M', | |||
|
595 | {'added': 2, | |||
|
596 | 'deleted': 0, | |||
|
597 | 'binary': False, | |||
|
598 | 'ops': {MOD_FILENODE: 'modified file'} | |||
|
599 | }), | |||
|
600 | ('trunk/src/TortoiseShell/ShellCache.h', 'M', | |||
|
601 | {'added': 50, | |||
|
602 | 'deleted': 1, | |||
|
603 | 'binary': False, | |||
|
604 | 'ops': {MOD_FILENODE: 'modified file'} | |||
|
605 | }), | |||
|
606 | ]), | |||
|
607 | ||||
|
608 | ||||
534 | # TODO: mikhail: do we still need this? |
|
609 | # TODO: mikhail: do we still need this? | |
535 | # ( |
|
610 | # ( | |
536 | # 'hg', |
|
611 | # 'hg', | |
@@ -579,7 +654,6 b' DIFF_FIXTURES = [' | |||||
579 | # 'pylons_app.egg-info/dependency_links.txt', 'A', { |
|
654 | # 'pylons_app.egg-info/dependency_links.txt', 'A', { | |
580 | # 'deleted': 0, 'binary': False, 'added': 1, 'ops': { |
|
655 | # 'deleted': 0, 'binary': False, 'added': 1, 'ops': { | |
581 | # 1: 'new file 100644'}}), |
|
656 | # 1: 'new file 100644'}}), | |
582 | # #TODO: |
|
|||
583 | # ] |
|
657 | # ] | |
584 | # ), |
|
658 | # ), | |
585 | ] |
|
659 | ] |
@@ -38,6 +38,7 b' from rhodecode.model.db import User, Rep' | |||||
38 | from rhodecode.model.meta import Session |
|
38 | from rhodecode.model.meta import Session | |
39 | from rhodecode.model.scm import ScmModel |
|
39 | from rhodecode.model.scm import ScmModel | |
40 | from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository |
|
40 | from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository | |
|
41 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |||
41 |
|
42 | |||
42 |
|
43 | |||
43 | log = logging.getLogger(__name__) |
|
44 | log = logging.getLogger(__name__) | |
@@ -372,3 +373,37 b' def repo_on_filesystem(repo_name):' | |||||
372 | repo = vcs.get_vcs_instance( |
|
373 | repo = vcs.get_vcs_instance( | |
373 | os.path.join(TESTS_TMP_PATH, repo_name), create=False) |
|
374 | os.path.join(TESTS_TMP_PATH, repo_name), create=False) | |
374 | return repo is not None |
|
375 | return repo is not None | |
|
376 | ||||
|
377 | ||||
|
378 | def commit_change( | |||
|
379 | repo, filename, content, message, vcs_type, parent=None, newfile=False): | |||
|
380 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN | |||
|
381 | ||||
|
382 | repo = Repository.get_by_repo_name(repo) | |||
|
383 | _commit = parent | |||
|
384 | if not parent: | |||
|
385 | _commit = EmptyCommit(alias=vcs_type) | |||
|
386 | ||||
|
387 | if newfile: | |||
|
388 | nodes = { | |||
|
389 | filename: { | |||
|
390 | 'content': content | |||
|
391 | } | |||
|
392 | } | |||
|
393 | commit = ScmModel().create_nodes( | |||
|
394 | user=TEST_USER_ADMIN_LOGIN, repo=repo, | |||
|
395 | message=message, | |||
|
396 | nodes=nodes, | |||
|
397 | parent_commit=_commit, | |||
|
398 | author=TEST_USER_ADMIN_LOGIN, | |||
|
399 | ) | |||
|
400 | else: | |||
|
401 | commit = ScmModel().commit_change( | |||
|
402 | repo=repo.scm_instance(), repo_name=repo.repo_name, | |||
|
403 | commit=parent, user=TEST_USER_ADMIN_LOGIN, | |||
|
404 | author=TEST_USER_ADMIN_LOGIN, | |||
|
405 | message=message, | |||
|
406 | content=content, | |||
|
407 | f_path=filename | |||
|
408 | ) | |||
|
409 | return commit |
@@ -359,14 +359,15 b' class TestSvnGetDiff:' | |||||
359 | ], ids=['file', 'dir']) |
|
359 | ], ids=['file', 'dir']) | |
360 | def test_diff_to_tagged_version(self, vcsbackend_svn, path, path1): |
|
360 | def test_diff_to_tagged_version(self, vcsbackend_svn, path, path1): | |
361 | repo = vcsbackend_svn['svn-simple-layout'] |
|
361 | repo = vcsbackend_svn['svn-simple-layout'] | |
362 |
commit = repo[- |
|
362 | commit1 = repo[-2] | |
363 | diff = repo.get_diff(commit, commit, path=path, path1=path1) |
|
363 | commit2 = repo[-1] | |
|
364 | diff = repo.get_diff(commit1, commit2, path=path, path1=path1) | |||
364 | assert diff.raw == self.expected_diff_v_0_2 |
|
365 | assert diff.raw == self.expected_diff_v_0_2 | |
365 |
|
366 | |||
366 | expected_diff_v_0_2 = '''Index: example.py |
|
367 | expected_diff_v_0_2 = '''Index: example.py | |
367 | =================================================================== |
|
368 | =================================================================== | |
368 | diff --git a/example.py b/example.py |
|
369 | diff --git a/example.py b/example.py | |
369 |
--- a/example.py\t(revision 2 |
|
370 | --- a/example.py\t(revision 25) | |
370 | +++ b/example.py\t(revision 26) |
|
371 | +++ b/example.py\t(revision 26) | |
371 | @@ -7,8 +7,12 @@ |
|
372 | @@ -7,8 +7,12 @@ | |
372 |
|
373 |
@@ -1,50 +0,0 b'' | |||||
1 | /* required */ |
|
|||
2 | .mergely-column textarea { width: 80px; height: 200px; } |
|
|||
3 | .mergely-column { float: left; } |
|
|||
4 | .mergely-margin { float: left; } |
|
|||
5 | .mergely-canvas { float: left; width: 28px; } |
|
|||
6 |
|
||||
7 | /* resizeable */ |
|
|||
8 | .mergely-resizer { width: 100%; height: 100%; } |
|
|||
9 |
|
||||
10 | /* style configuration */ |
|
|||
11 | .mergely-column { border: 1px solid #ccc; } |
|
|||
12 | .mergely-active { border: 1px solid #a3d1ff; } |
|
|||
13 |
|
||||
14 | .mergely.a,.mergely.d,.mergely.c { color: #000; } |
|
|||
15 |
|
||||
16 | .mergely.a.rhs.start { border-top: 1px solid #a3d1ff; } |
|
|||
17 | .mergely.a.lhs.start.end, |
|
|||
18 | .mergely.a.rhs.end { border-bottom: 1px solid #a3d1ff; } |
|
|||
19 | .mergely.a.rhs { background-color: #ddeeff; } |
|
|||
20 | .mergely.a.lhs.start.end.first { border-bottom: 0; border-top: 1px solid #a3d1ff; } |
|
|||
21 |
|
||||
22 | .mergely.d.lhs { background-color: #ffe9e9; } |
|
|||
23 | .mergely.d.lhs.end, |
|
|||
24 | .mergely.d.rhs.start.end { border-bottom: 1px solid #f8e8e8; } |
|
|||
25 | .mergely.d.rhs.start.end.first { border-bottom: 0; border-top: 1px solid #f8e8e8; } |
|
|||
26 | .mergely.d.lhs.start { border-top: 1px solid #f8e8e8; } |
|
|||
27 |
|
||||
28 | .mergely.c.lhs, |
|
|||
29 | .mergely.c.rhs { background-color: #fafafa; } |
|
|||
30 | .mergely.c.lhs.start, |
|
|||
31 | .mergely.c.rhs.start { border-top: 1px solid #a3a3a3; } |
|
|||
32 | .mergely.c.lhs.end, |
|
|||
33 | .mergely.c.rhs.end { border-bottom: 1px solid #a3a3a3; } |
|
|||
34 |
|
||||
35 | .mergely.ch.a.rhs { background-color: #ddeeff; } |
|
|||
36 | .mergely.ch.d.lhs { background-color: #ffe9e9; text-decoration: line-through; color: red !important; } |
|
|||
37 |
|
||||
38 | .mergely-margin #compare-lhs-margin, |
|
|||
39 | .mergely-margin #compare-rhs-margin { |
|
|||
40 | cursor: pointer |
|
|||
41 | } |
|
|||
42 |
|
||||
43 | .mergely.current.start { border-top: 1px solid #000 !important; } |
|
|||
44 | .mergely.current.end { border-bottom: 1px solid #000 !important; } |
|
|||
45 | .mergely.current.lhs.a.start.end, |
|
|||
46 | .mergely.current.rhs.d.start.end { border-top: 0 !important; } |
|
|||
47 | .mergely.current.CodeMirror-linenumber { color: #F9F9F9; font-weight: bold; background-color: #777; } |
|
|||
48 |
|
||||
49 | .CodeMirror-linenumber { cursor: pointer; } |
|
|||
50 | .CodeMirror-code { color: #717171; } No newline at end of file |
|
This diff has been collapsed as it changes many lines, (1669 lines changed) Show them Hide them | |||||
@@ -1,1669 +0,0 b'' | |||||
1 | "use strict"; |
|
|||
2 |
|
||||
3 | (function( window, document, jQuery, CodeMirror ){ |
|
|||
4 |
|
||||
5 | var Mgly = {}; |
|
|||
6 |
|
||||
7 | Mgly.Timer = function(){ |
|
|||
8 | var self = this; |
|
|||
9 | self.start = function() { self.t0 = new Date().getTime(); }; |
|
|||
10 | self.stop = function() { |
|
|||
11 | var t1 = new Date().getTime(); |
|
|||
12 | var d = t1 - self.t0; |
|
|||
13 | self.t0 = t1; |
|
|||
14 | return d; |
|
|||
15 | }; |
|
|||
16 | self.start(); |
|
|||
17 | }; |
|
|||
18 |
|
||||
19 | Mgly.ChangeExpression = new RegExp(/(^(?![><\-])*\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/); |
|
|||
20 |
|
||||
21 | Mgly.DiffParser = function(diff) { |
|
|||
22 | var changes = []; |
|
|||
23 | var change_id = 0; |
|
|||
24 | // parse diff |
|
|||
25 | var diff_lines = diff.split(/\n/); |
|
|||
26 | for (var i = 0; i < diff_lines.length; ++i) { |
|
|||
27 | if (diff_lines[i].length == 0) continue; |
|
|||
28 | var change = {}; |
|
|||
29 | var test = Mgly.ChangeExpression.exec(diff_lines[i]); |
|
|||
30 | if (test == null) continue; |
|
|||
31 | // lines are zero-based |
|
|||
32 | var fr = test[1].split(','); |
|
|||
33 | change['lhs-line-from'] = fr[0] - 1; |
|
|||
34 | if (fr.length == 1) change['lhs-line-to'] = fr[0] - 1; |
|
|||
35 | else change['lhs-line-to'] = fr[1] - 1; |
|
|||
36 | var to = test[3].split(','); |
|
|||
37 | change['rhs-line-from'] = to[0] - 1; |
|
|||
38 | if (to.length == 1) change['rhs-line-to'] = to[0] - 1; |
|
|||
39 | else change['rhs-line-to'] = to[1] - 1; |
|
|||
40 | change['op'] = test[2]; |
|
|||
41 | changes[change_id++] = change; |
|
|||
42 | } |
|
|||
43 | return changes; |
|
|||
44 | }; |
|
|||
45 |
|
||||
46 | Mgly.sizeOf = function(obj) { |
|
|||
47 | var size = 0, key; |
|
|||
48 | for (key in obj) { |
|
|||
49 | if (obj.hasOwnProperty(key)) size++; |
|
|||
50 | } |
|
|||
51 | return size; |
|
|||
52 | }; |
|
|||
53 |
|
||||
54 | Mgly.LCS = function(x, y) { |
|
|||
55 | this.x = x.replace(/[ ]{1}/g, '\n'); |
|
|||
56 | this.y = y.replace(/[ ]{1}/g, '\n'); |
|
|||
57 | }; |
|
|||
58 |
|
||||
59 | jQuery.extend(Mgly.LCS.prototype, { |
|
|||
60 | clear: function() { this.ready = 0; }, |
|
|||
61 | diff: function(added, removed) { |
|
|||
62 | var d = new Mgly.diff(this.x, this.y, {ignorews: false}); |
|
|||
63 | var changes = Mgly.DiffParser(d.normal_form()); |
|
|||
64 | var li = 0, lj = 0; |
|
|||
65 | for (var i = 0; i < changes.length; ++i) { |
|
|||
66 | var change = changes[i]; |
|
|||
67 | if (change.op != 'a') { |
|
|||
68 | // find the starting index of the line |
|
|||
69 | li = d.getLines('lhs').slice(0, change['lhs-line-from']).join(' ').length; |
|
|||
70 | // get the index of the the span of the change |
|
|||
71 | lj = change['lhs-line-to'] + 1; |
|
|||
72 | // get the changed text |
|
|||
73 | var lchange = d.getLines('lhs').slice(change['lhs-line-from'], lj).join(' '); |
|
|||
74 | if (change.op == 'd') lchange += ' ';// include the leading space |
|
|||
75 | else if (li > 0 && change.op == 'c') li += 1; // ignore leading space if not first word |
|
|||
76 | // output the changed index and text |
|
|||
77 | removed(li, li + lchange.length); |
|
|||
78 | } |
|
|||
79 | if (change.op != 'd') { |
|
|||
80 | // find the starting index of the line |
|
|||
81 | li = d.getLines('rhs').slice(0, change['rhs-line-from']).join(' ').length; |
|
|||
82 | // get the index of the the span of the change |
|
|||
83 | lj = change['rhs-line-to'] + 1; |
|
|||
84 | // get the changed text |
|
|||
85 | var rchange = d.getLines('rhs').slice(change['rhs-line-from'], lj).join(' '); |
|
|||
86 | if (change.op == 'a') rchange += ' ';// include the leading space |
|
|||
87 | else if (li > 0 && change.op == 'c') li += 1; // ignore leading space if not first word |
|
|||
88 | // output the changed index and text |
|
|||
89 | added(li, li + rchange.length); |
|
|||
90 | } |
|
|||
91 | } |
|
|||
92 | } |
|
|||
93 | }); |
|
|||
94 |
|
||||
95 | Mgly.CodeifyText = function(settings) { |
|
|||
96 | this._max_code = 0; |
|
|||
97 | this._diff_codes = {}; |
|
|||
98 | this.ctxs = {}; |
|
|||
99 | this.options = {ignorews: false}; |
|
|||
100 | jQuery.extend(this, settings); |
|
|||
101 | this.lhs = settings.lhs.split('\n'); |
|
|||
102 | this.rhs = settings.rhs.split('\n'); |
|
|||
103 | }; |
|
|||
104 |
|
||||
105 | jQuery.extend(Mgly.CodeifyText.prototype, { |
|
|||
106 | getCodes: function(side) { |
|
|||
107 | if (!this.ctxs.hasOwnProperty(side)) { |
|
|||
108 | var ctx = this._diff_ctx(this[side]); |
|
|||
109 | this.ctxs[side] = ctx; |
|
|||
110 | ctx.codes.length = Object.keys(ctx.codes).length; |
|
|||
111 | } |
|
|||
112 | return this.ctxs[side].codes; |
|
|||
113 | }, |
|
|||
114 | getLines: function(side) { |
|
|||
115 | return this.ctxs[side].lines; |
|
|||
116 | }, |
|
|||
117 | _diff_ctx: function(lines) { |
|
|||
118 | var ctx = {i: 0, codes: {}, lines: lines}; |
|
|||
119 | this._codeify(lines, ctx); |
|
|||
120 | return ctx; |
|
|||
121 | }, |
|
|||
122 | _codeify: function(lines, ctx) { |
|
|||
123 | var code = this._max_code; |
|
|||
124 | for (var i = 0; i < lines.length; ++i) { |
|
|||
125 | var line = lines[i]; |
|
|||
126 | if (this.options.ignorews) { |
|
|||
127 | line = line.replace(/\s+/g, ''); |
|
|||
128 | } |
|
|||
129 | var aCode = this._diff_codes[line]; |
|
|||
130 | if (aCode != undefined) { |
|
|||
131 | ctx.codes[i] = aCode; |
|
|||
132 | } |
|
|||
133 | else { |
|
|||
134 | this._max_code++; |
|
|||
135 | this._diff_codes[line] = this._max_code; |
|
|||
136 | ctx.codes[i] = this._max_code; |
|
|||
137 | } |
|
|||
138 | } |
|
|||
139 | } |
|
|||
140 | }); |
|
|||
141 |
|
||||
142 | Mgly.diff = function(lhs, rhs, options) { |
|
|||
143 | var opts = jQuery.extend({ignorews: false}, options); |
|
|||
144 | this.codeify = new Mgly.CodeifyText({ |
|
|||
145 | lhs: lhs, |
|
|||
146 | rhs: rhs, |
|
|||
147 | options: opts |
|
|||
148 | }); |
|
|||
149 | var lhs_ctx = { |
|
|||
150 | codes: this.codeify.getCodes('lhs'), |
|
|||
151 | modified: {} |
|
|||
152 | }; |
|
|||
153 | var rhs_ctx = { |
|
|||
154 | codes: this.codeify.getCodes('rhs'), |
|
|||
155 | modified: {} |
|
|||
156 | }; |
|
|||
157 | var max = (lhs_ctx.codes.length + rhs_ctx.codes.length + 1); |
|
|||
158 | var vector_d = []; |
|
|||
159 | var vector_u = []; |
|
|||
160 | this._lcs(lhs_ctx, 0, lhs_ctx.codes.length, rhs_ctx, 0, rhs_ctx.codes.length, vector_u, vector_d); |
|
|||
161 | this._optimize(lhs_ctx); |
|
|||
162 | this._optimize(rhs_ctx); |
|
|||
163 | this.items = this._create_diffs(lhs_ctx, rhs_ctx); |
|
|||
164 | }; |
|
|||
165 |
|
||||
166 | jQuery.extend(Mgly.diff.prototype, { |
|
|||
167 | changes: function() { return this.items; }, |
|
|||
168 | getLines: function(side) { |
|
|||
169 | return this.codeify.getLines(side); |
|
|||
170 | }, |
|
|||
171 | normal_form: function() { |
|
|||
172 | var nf = ''; |
|
|||
173 | for (var index = 0; index < this.items.length; ++index) { |
|
|||
174 | var item = this.items[index]; |
|
|||
175 | var lhs_str = ''; |
|
|||
176 | var rhs_str = ''; |
|
|||
177 | var change = 'c'; |
|
|||
178 | if (item.lhs_deleted_count == 0 && item.rhs_inserted_count > 0) change = 'a'; |
|
|||
179 | else if (item.lhs_deleted_count > 0 && item.rhs_inserted_count == 0) change = 'd'; |
|
|||
180 |
|
||||
181 | if (item.lhs_deleted_count == 1) lhs_str = item.lhs_start + 1; |
|
|||
182 | else if (item.lhs_deleted_count == 0) lhs_str = item.lhs_start; |
|
|||
183 | else lhs_str = (item.lhs_start + 1) + ',' + (item.lhs_start + item.lhs_deleted_count); |
|
|||
184 |
|
||||
185 | if (item.rhs_inserted_count == 1) rhs_str = item.rhs_start + 1; |
|
|||
186 | else if (item.rhs_inserted_count == 0) rhs_str = item.rhs_start; |
|
|||
187 | else rhs_str = (item.rhs_start + 1) + ',' + (item.rhs_start + item.rhs_inserted_count); |
|
|||
188 | nf += lhs_str + change + rhs_str + '\n'; |
|
|||
189 |
|
||||
190 | var lhs_lines = this.getLines('lhs'); |
|
|||
191 | var rhs_lines = this.getLines('rhs'); |
|
|||
192 | if (rhs_lines && lhs_lines) { |
|
|||
193 | var i; |
|
|||
194 | // if rhs/lhs lines have been retained, output contextual diff |
|
|||
195 | for (i = item.lhs_start; i < item.lhs_start + item.lhs_deleted_count; ++i) { |
|
|||
196 | nf += '< ' + lhs_lines[i] + '\n'; |
|
|||
197 | } |
|
|||
198 | if (item.rhs_inserted_count && item.lhs_deleted_count) nf += '---\n'; |
|
|||
199 | for (i = item.rhs_start; i < item.rhs_start + item.rhs_inserted_count; ++i) { |
|
|||
200 | nf += '> ' + rhs_lines[i] + '\n'; |
|
|||
201 | } |
|
|||
202 | } |
|
|||
203 | } |
|
|||
204 | return nf; |
|
|||
205 | }, |
|
|||
206 | _lcs: function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d) { |
|
|||
207 | while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs_ctx.codes[lhs_lower] == rhs_ctx.codes[rhs_lower]) ) { |
|
|||
208 | ++lhs_lower; |
|
|||
209 | ++rhs_lower; |
|
|||
210 | } |
|
|||
211 | while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs_ctx.codes[lhs_upper - 1] == rhs_ctx.codes[rhs_upper - 1]) ) { |
|
|||
212 | --lhs_upper; |
|
|||
213 | --rhs_upper; |
|
|||
214 | } |
|
|||
215 | if (lhs_lower == lhs_upper) { |
|
|||
216 | while (rhs_lower < rhs_upper) { |
|
|||
217 | rhs_ctx.modified[ rhs_lower++ ] = true; |
|
|||
218 | } |
|
|||
219 | } |
|
|||
220 | else if (rhs_lower == rhs_upper) { |
|
|||
221 | while (lhs_lower < lhs_upper) { |
|
|||
222 | lhs_ctx.modified[ lhs_lower++ ] = true; |
|
|||
223 | } |
|
|||
224 | } |
|
|||
225 | else { |
|
|||
226 | var sms = this._sms(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d); |
|
|||
227 | this._lcs(lhs_ctx, lhs_lower, sms.x, rhs_ctx, rhs_lower, sms.y, vector_u, vector_d); |
|
|||
228 | this._lcs(lhs_ctx, sms.x, lhs_upper, rhs_ctx, sms.y, rhs_upper, vector_u, vector_d); |
|
|||
229 | } |
|
|||
230 | }, |
|
|||
231 | _sms: function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d) { |
|
|||
232 | var max = lhs_ctx.codes.length + rhs_ctx.codes.length + 1; |
|
|||
233 | var kdown = lhs_lower - rhs_lower; |
|
|||
234 | var kup = lhs_upper - rhs_upper; |
|
|||
235 | var delta = (lhs_upper - lhs_lower) - (rhs_upper - rhs_lower); |
|
|||
236 | var odd = (delta & 1) != 0; |
|
|||
237 | var offset_down = max - kdown; |
|
|||
238 | var offset_up = max - kup; |
|
|||
239 | var maxd = ((lhs_upper - lhs_lower + rhs_upper - rhs_lower) / 2) + 1; |
|
|||
240 | vector_d[ offset_down + kdown + 1 ] = lhs_lower; |
|
|||
241 | vector_u[ offset_up + kup - 1 ] = lhs_upper; |
|
|||
242 | var ret = {x:0,y:0}, d, k, x, y; |
|
|||
243 | for (d = 0; d <= maxd; ++d) { |
|
|||
244 | for (k = kdown - d; k <= kdown + d; k += 2) { |
|
|||
245 | if (k == kdown - d) { |
|
|||
246 | x = vector_d[ offset_down + k + 1 ];//down |
|
|||
247 | } |
|
|||
248 | else { |
|
|||
249 | x = vector_d[ offset_down + k - 1 ] + 1;//right |
|
|||
250 | if ((k < (kdown + d)) && (vector_d[ offset_down + k + 1 ] >= x)) { |
|
|||
251 | x = vector_d[ offset_down + k + 1 ];//down |
|
|||
252 | } |
|
|||
253 | } |
|
|||
254 | y = x - k; |
|
|||
255 | // find the end of the furthest reaching forward D-path in diagonal k. |
|
|||
256 | while ((x < lhs_upper) && (y < rhs_upper) && (lhs_ctx.codes[x] == rhs_ctx.codes[y])) { |
|
|||
257 | x++; y++; |
|
|||
258 | } |
|
|||
259 | vector_d[ offset_down + k ] = x; |
|
|||
260 | // overlap ? |
|
|||
261 | if (odd && (kup - d < k) && (k < kup + d)) { |
|
|||
262 | if (vector_u[offset_up + k] <= vector_d[offset_down + k]) { |
|
|||
263 | ret.x = vector_d[offset_down + k]; |
|
|||
264 | ret.y = vector_d[offset_down + k] - k; |
|
|||
265 | return (ret); |
|
|||
266 | } |
|
|||
267 | } |
|
|||
268 | } |
|
|||
269 | // Extend the reverse path. |
|
|||
270 | for (k = kup - d; k <= kup + d; k += 2) { |
|
|||
271 | // find the only or better starting point |
|
|||
272 | if (k == kup + d) { |
|
|||
273 | x = vector_u[offset_up + k - 1]; // up |
|
|||
274 | } else { |
|
|||
275 | x = vector_u[offset_up + k + 1] - 1; // left |
|
|||
276 | if ((k > kup - d) && (vector_u[offset_up + k - 1] < x)) |
|
|||
277 | x = vector_u[offset_up + k - 1]; // up |
|
|||
278 | } |
|
|||
279 | y = x - k; |
|
|||
280 | while ((x > lhs_lower) && (y > rhs_lower) && (lhs_ctx.codes[x - 1] == rhs_ctx.codes[y - 1])) { |
|
|||
281 | // diagonal |
|
|||
282 | x--; |
|
|||
283 | y--; |
|
|||
284 | } |
|
|||
285 | vector_u[offset_up + k] = x; |
|
|||
286 | // overlap ? |
|
|||
287 | if (!odd && (kdown - d <= k) && (k <= kdown + d)) { |
|
|||
288 | if (vector_u[offset_up + k] <= vector_d[offset_down + k]) { |
|
|||
289 | ret.x = vector_d[offset_down + k]; |
|
|||
290 | ret.y = vector_d[offset_down + k] - k; |
|
|||
291 | return (ret); |
|
|||
292 | } |
|
|||
293 | } |
|
|||
294 | } |
|
|||
295 | } |
|
|||
296 | throw "the algorithm should never come here."; |
|
|||
297 | }, |
|
|||
298 | _optimize: function(ctx) { |
|
|||
299 | var start = 0, end = 0; |
|
|||
300 | while (start < ctx.codes.length) { |
|
|||
301 | while ((start < ctx.codes.length) && (ctx.modified[start] == undefined || ctx.modified[start] == false)) { |
|
|||
302 | start++; |
|
|||
303 | } |
|
|||
304 | end = start; |
|
|||
305 | while ((end < ctx.codes.length) && (ctx.modified[end] == true)) { |
|
|||
306 | end++; |
|
|||
307 | } |
|
|||
308 | if ((end < ctx.codes.length) && (ctx.codes[start] == ctx.codes[end])) { |
|
|||
309 | ctx.modified[start] = false; |
|
|||
310 | ctx.modified[end] = true; |
|
|||
311 | } |
|
|||
312 | else { |
|
|||
313 | start = end; |
|
|||
314 | } |
|
|||
315 | } |
|
|||
316 | }, |
|
|||
317 | _create_diffs: function(lhs_ctx, rhs_ctx) { |
|
|||
318 | var items = []; |
|
|||
319 | var lhs_start = 0, rhs_start = 0; |
|
|||
320 | var lhs_line = 0, rhs_line = 0; |
|
|||
321 |
|
||||
322 | while (lhs_line < lhs_ctx.codes.length || rhs_line < rhs_ctx.codes.length) { |
|
|||
323 | if ((lhs_line < lhs_ctx.codes.length) && (!lhs_ctx.modified[lhs_line]) |
|
|||
324 | && (rhs_line < rhs_ctx.codes.length) && (!rhs_ctx.modified[rhs_line])) { |
|
|||
325 | // equal lines |
|
|||
326 | lhs_line++; |
|
|||
327 | rhs_line++; |
|
|||
328 | } |
|
|||
329 | else { |
|
|||
330 | // maybe deleted and/or inserted lines |
|
|||
331 | lhs_start = lhs_line; |
|
|||
332 | rhs_start = rhs_line; |
|
|||
333 |
|
||||
334 | while (lhs_line < lhs_ctx.codes.length && (rhs_line >= rhs_ctx.codes.length || lhs_ctx.modified[lhs_line])) |
|
|||
335 | lhs_line++; |
|
|||
336 |
|
||||
337 | while (rhs_line < rhs_ctx.codes.length && (lhs_line >= lhs_ctx.codes.length || rhs_ctx.modified[rhs_line])) |
|
|||
338 | rhs_line++; |
|
|||
339 |
|
||||
340 | if ((lhs_start < lhs_line) || (rhs_start < rhs_line)) { |
|
|||
341 | // store a new difference-item |
|
|||
342 | items.push({ |
|
|||
343 | lhs_start: lhs_start, |
|
|||
344 | rhs_start: rhs_start, |
|
|||
345 | lhs_deleted_count: lhs_line - lhs_start, |
|
|||
346 | rhs_inserted_count: rhs_line - rhs_start |
|
|||
347 | }); |
|
|||
348 | } |
|
|||
349 | } |
|
|||
350 | } |
|
|||
351 | return items; |
|
|||
352 | } |
|
|||
353 | }); |
|
|||
354 |
|
||||
355 | Mgly.mergely = function(el, options) { |
|
|||
356 | if (el) { |
|
|||
357 | this.init(el, options); |
|
|||
358 | } |
|
|||
359 | }; |
|
|||
360 |
|
||||
361 | jQuery.extend(Mgly.mergely.prototype, { |
|
|||
362 | name: 'mergely', |
|
|||
363 | //http://jupiterjs.com/news/writing-the-perfect-jquery-plugin |
|
|||
364 | init: function(el, options) { |
|
|||
365 | this.diffView = new Mgly.CodeMirrorDiffView(el, options); |
|
|||
366 | this.bind(el); |
|
|||
367 | }, |
|
|||
368 | bind: function(el) { |
|
|||
369 | this.diffView.bind(el); |
|
|||
370 | } |
|
|||
371 | }); |
|
|||
372 |
|
||||
373 | Mgly.CodeMirrorDiffView = function(el, options) { |
|
|||
374 | CodeMirror.defineExtension('centerOnCursor', function() { |
|
|||
375 | var coords = this.cursorCoords(null, 'local'); |
|
|||
376 | this.scrollTo(null, |
|
|||
377 | (coords.y + coords.yBot) / 2 - (this.getScrollerElement().clientHeight / 2)); |
|
|||
378 | }); |
|
|||
379 | this.init(el, options); |
|
|||
380 | }; |
|
|||
381 |
|
||||
382 | jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { |
|
|||
383 | init: function(el, options) { |
|
|||
384 | this.settings = { |
|
|||
385 | autoupdate: true, |
|
|||
386 | autoresize: true, |
|
|||
387 | rhs_margin: 'right', |
|
|||
388 | wrap_lines: false, |
|
|||
389 | line_numbers: true, |
|
|||
390 | lcs: true, |
|
|||
391 | sidebar: true, |
|
|||
392 | viewport: false, |
|
|||
393 | ignorews: false, |
|
|||
394 | fadein: 'fast', |
|
|||
395 | editor_width: '650px', |
|
|||
396 | editor_height: '400px', |
|
|||
397 | resize_timeout: 500, |
|
|||
398 | change_timeout: 150, |
|
|||
399 | fgcolor: {a:'#4ba3fa',c:'#a3a3a3',d:'#ff7f7f', // color for differences (soft color) |
|
|||
400 | ca:'#4b73ff',cc:'#434343',cd:'#ff4f4f'}, // color for currently active difference (bright color) |
|
|||
401 | bgcolor: '#eee', |
|
|||
402 | vpcolor: 'rgba(0, 0, 200, 0.5)', |
|
|||
403 | lhs: function(setValue) { }, |
|
|||
404 | rhs: function(setValue) { }, |
|
|||
405 | loaded: function() { }, |
|
|||
406 | _auto_width: function(w) { return w; }, |
|
|||
407 | resize: function(init) { |
|
|||
408 | var scrollbar = init ? 16 : 0; |
|
|||
409 | var w = jQuery(el).parent().width() + scrollbar, h = 0; |
|
|||
410 | if (this.width == 'auto') { |
|
|||
411 | w = this._auto_width(w); |
|
|||
412 | } |
|
|||
413 | else { |
|
|||
414 | w = this.width; |
|
|||
415 | this.editor_width = w; |
|
|||
416 | } |
|
|||
417 | if (this.height == 'auto') { |
|
|||
418 | //h = this._auto_height(h); |
|
|||
419 | h = jQuery(el).parent().height(); |
|
|||
420 | } |
|
|||
421 | else { |
|
|||
422 | h = this.height; |
|
|||
423 | this.editor_height = h; |
|
|||
424 | } |
|
|||
425 | var content_width = w / 2.0 - 2 * 8 - 8; |
|
|||
426 | var content_height = h; |
|
|||
427 | var self = jQuery(el); |
|
|||
428 | self.find('.mergely-column').css({ width: content_width + 'px' }); |
|
|||
429 | self.find('.mergely-column, .mergely-canvas, .mergely-margin, .mergely-column textarea, .CodeMirror-scroll, .cm-s-default').css({ height: content_height + 'px' }); |
|
|||
430 | self.find('.mergely-canvas').css({ height: content_height + 'px' }); |
|
|||
431 | self.find('.mergely-column textarea').css({ width: content_width + 'px' }); |
|
|||
432 | self.css({ width: w, height: h, clear: 'both' }); |
|
|||
433 | if (self.css('display') == 'none') { |
|
|||
434 | if (this.fadein != false) self.fadeIn(this.fadein); |
|
|||
435 | else self.show(); |
|
|||
436 | if (this.loaded) this.loaded(); |
|
|||
437 | } |
|
|||
438 | if (this.resized) this.resized(); |
|
|||
439 | }, |
|
|||
440 | _debug: '', //scroll,draw,calc,diff,markup,change |
|
|||
441 | resized: function() { } |
|
|||
442 | }; |
|
|||
443 | var cmsettings = { |
|
|||
444 | mode: 'text/plain', |
|
|||
445 | readOnly: false, |
|
|||
446 | lineWrapping: this.settings.wrap_lines, |
|
|||
447 | lineNumbers: this.settings.line_numbers, |
|
|||
448 | gutters: ['merge', 'CodeMirror-linenumbers'] |
|
|||
449 | }; |
|
|||
450 | this.lhs_cmsettings = {}; |
|
|||
451 | this.rhs_cmsettings = {}; |
|
|||
452 |
|
||||
453 | // save this element for faster queries |
|
|||
454 | this.element = jQuery(el); |
|
|||
455 |
|
||||
456 | // save options if there are any |
|
|||
457 | if (options && options.cmsettings) jQuery.extend(this.lhs_cmsettings, cmsettings, options.cmsettings, options.lhs_cmsettings); |
|
|||
458 | if (options && options.cmsettings) jQuery.extend(this.rhs_cmsettings, cmsettings, options.cmsettings, options.rhs_cmsettings); |
|
|||
459 | //if (options) jQuery.extend(this.settings, options); |
|
|||
460 |
|
||||
461 | // bind if the element is destroyed |
|
|||
462 | this.element.bind('destroyed', jQuery.proxy(this.teardown, this)); |
|
|||
463 |
|
||||
464 | // save this instance in jQuery data, binding this view to the node |
|
|||
465 | jQuery.data(el, 'mergely', this); |
|
|||
466 |
|
||||
467 | this._setOptions(options); |
|
|||
468 | }, |
|
|||
469 | unbind: function() { |
|
|||
470 | if (this.changed_timeout != null) clearTimeout(this.changed_timeout); |
|
|||
471 | this.editor[this.id + '-lhs'].toTextArea(); |
|
|||
472 | this.editor[this.id + '-rhs'].toTextArea(); |
|
|||
473 | jQuery(window).off('.mergely'); |
|
|||
474 | }, |
|
|||
475 | destroy: function() { |
|
|||
476 | this.element.unbind('destroyed', this.teardown); |
|
|||
477 | this.teardown(); |
|
|||
478 | }, |
|
|||
479 | teardown: function() { |
|
|||
480 | this.unbind(); |
|
|||
481 | }, |
|
|||
482 | lhs: function(text) { |
|
|||
483 | this.editor[this.id + '-lhs'].setValue(text); |
|
|||
484 | }, |
|
|||
485 | rhs: function(text) { |
|
|||
486 | this.editor[this.id + '-rhs'].setValue(text); |
|
|||
487 | }, |
|
|||
488 | update: function() { |
|
|||
489 | this._changing(this.id + '-lhs', this.id + '-rhs'); |
|
|||
490 | }, |
|
|||
491 | unmarkup: function() { |
|
|||
492 | this._clear(); |
|
|||
493 | }, |
|
|||
494 | scrollToDiff: function(direction) { |
|
|||
495 | if (!this.changes.length) return; |
|
|||
496 | if (direction == 'next') { |
|
|||
497 | this._current_diff = Math.min(++this._current_diff, this.changes.length - 1); |
|
|||
498 | } |
|
|||
499 | else if (direction == 'prev') { |
|
|||
500 | this._current_diff = Math.max(--this._current_diff, 0); |
|
|||
501 | } |
|
|||
502 | this._scroll_to_change(this.changes[this._current_diff]); |
|
|||
503 | this._changed(this.id + '-lhs', this.id + '-rhs'); |
|
|||
504 | }, |
|
|||
505 | mergeCurrentChange: function(side) { |
|
|||
506 | if (!this.changes.length) return; |
|
|||
507 | if (side == 'lhs' && !this.lhs_cmsettings.readOnly) { |
|
|||
508 | this._merge_change(this.changes[this._current_diff], 'rhs', 'lhs'); |
|
|||
509 | } |
|
|||
510 | else if (side == 'rhs' && !this.rhs_cmsettings.readOnly) { |
|
|||
511 | this._merge_change(this.changes[this._current_diff], 'lhs', 'rhs'); |
|
|||
512 | } |
|
|||
513 | }, |
|
|||
514 | scrollTo: function(side, num) { |
|
|||
515 | var le = this.editor[this.id + '-lhs']; |
|
|||
516 | var re = this.editor[this.id + '-rhs']; |
|
|||
517 | if (side == 'lhs') { |
|
|||
518 | le.setCursor(num); |
|
|||
519 | le.centerOnCursor(); |
|
|||
520 | } |
|
|||
521 | else { |
|
|||
522 | re.setCursor(num); |
|
|||
523 | re.centerOnCursor(); |
|
|||
524 | } |
|
|||
525 | }, |
|
|||
526 | _setOptions: function(opts) { |
|
|||
527 | jQuery.extend(this.settings, opts); |
|
|||
528 | if (this.settings.hasOwnProperty('rhs_margin')) { |
|
|||
529 | // dynamically swap the margin |
|
|||
530 | if (this.settings.rhs_margin == 'left') { |
|
|||
531 | this.element.find('.mergely-margin:last-child').insertAfter( |
|
|||
532 | this.element.find('.mergely-canvas')); |
|
|||
533 | } |
|
|||
534 | else { |
|
|||
535 | var target = this.element.find('.mergely-margin').last(); |
|
|||
536 | target.appendTo(target.parent()); |
|
|||
537 | } |
|
|||
538 | } |
|
|||
539 | if (this.settings.hasOwnProperty('sidebar')) { |
|
|||
540 | // dynamically enable sidebars |
|
|||
541 | if (this.settings.sidebar) { |
|
|||
542 | this.element.find('.mergely-margin').css({display: 'block'}); |
|
|||
543 | } |
|
|||
544 | else { |
|
|||
545 | this.element.find('.mergely-margin').css({display: 'none'}); |
|
|||
546 | } |
|
|||
547 | } |
|
|||
548 | var le, re; |
|
|||
549 | if (this.settings.hasOwnProperty('wrap_lines')) { |
|
|||
550 | if (this.editor) { |
|
|||
551 | le = this.editor[this.id + '-lhs']; |
|
|||
552 | re = this.editor[this.id + '-rhs']; |
|
|||
553 | le.setOption('lineWrapping', this.settings.wrap_lines); |
|
|||
554 | re.setOption('lineWrapping', this.settings.wrap_lines); |
|
|||
555 | } |
|
|||
556 | } |
|
|||
557 | if (this.settings.hasOwnProperty('line_numbers')) { |
|
|||
558 | if (this.editor) { |
|
|||
559 | le = this.editor[this.id + '-lhs']; |
|
|||
560 | re = this.editor[this.id + '-rhs']; |
|
|||
561 | le.setOption('lineNumbers', this.settings.line_numbers); |
|
|||
562 | re.setOption('lineNumbers', this.settings.line_numbers); |
|
|||
563 | } |
|
|||
564 | } |
|
|||
565 | }, |
|
|||
566 | options: function(opts) { |
|
|||
567 | if (opts) { |
|
|||
568 | this._setOptions(opts); |
|
|||
569 | if (this.settings.autoresize) this.resize(); |
|
|||
570 | if (this.settings.autoupdate) this.update(); |
|
|||
571 | } |
|
|||
572 | else { |
|
|||
573 | return this.settings; |
|
|||
574 | } |
|
|||
575 | }, |
|
|||
576 | swap: function() { |
|
|||
577 | if (this.lhs_cmsettings.readOnly || this.rhs_cmsettings.readOnly) return; |
|
|||
578 | var le = this.editor[this.id + '-lhs']; |
|
|||
579 | var re = this.editor[this.id + '-rhs']; |
|
|||
580 | var tmp = re.getValue(); |
|
|||
581 | re.setValue(le.getValue()); |
|
|||
582 | le.setValue(tmp); |
|
|||
583 | }, |
|
|||
584 | merge: function(side) { |
|
|||
585 | var le = this.editor[this.id + '-lhs']; |
|
|||
586 | var re = this.editor[this.id + '-rhs']; |
|
|||
587 | if (side == 'lhs' && !this.lhs_cmsettings.readOnly) le.setValue(re.getValue()); |
|
|||
588 | else if (!this.rhs_cmsettings.readOnly) re.setValue(le.getValue()); |
|
|||
589 | }, |
|
|||
590 | get: function(side) { |
|
|||
591 | var ed = this.editor[this.id + '-' + side]; |
|
|||
592 | var t = ed.getValue(); |
|
|||
593 | if (t == undefined) return ''; |
|
|||
594 | return t; |
|
|||
595 | }, |
|
|||
596 | clear: function(side) { |
|
|||
597 | if (side == 'lhs' && this.lhs_cmsettings.readOnly) return; |
|
|||
598 | if (side == 'rhs' && this.rhs_cmsettings.readOnly) return; |
|
|||
599 | var ed = this.editor[this.id + '-' + side]; |
|
|||
600 | ed.setValue(''); |
|
|||
601 | }, |
|
|||
602 | cm: function(side) { |
|
|||
603 | return this.editor[this.id + '-' + side]; |
|
|||
604 | }, |
|
|||
605 | search: function(side, query, direction) { |
|
|||
606 | var le = this.editor[this.id + '-lhs']; |
|
|||
607 | var re = this.editor[this.id + '-rhs']; |
|
|||
608 | var editor; |
|
|||
609 | if (side == 'lhs') editor = le; |
|
|||
610 | else editor = re; |
|
|||
611 | direction = (direction == 'prev') ? 'findPrevious' : 'findNext'; |
|
|||
612 | if ((editor.getSelection().length == 0) || (this.prev_query[side] != query)) { |
|
|||
613 | this.cursor[this.id] = editor.getSearchCursor(query, { line: 0, ch: 0 }, false); |
|
|||
614 | this.prev_query[side] = query; |
|
|||
615 | } |
|
|||
616 | var cursor = this.cursor[this.id]; |
|
|||
617 |
|
||||
618 | if (cursor[direction]()) { |
|
|||
619 | editor.setSelection(cursor.from(), cursor.to()); |
|
|||
620 | } |
|
|||
621 | else { |
|
|||
622 | cursor = editor.getSearchCursor(query, { line: 0, ch: 0 }, false); |
|
|||
623 | } |
|
|||
624 | }, |
|
|||
625 | resize: function() { |
|
|||
626 | this.settings.resize(); |
|
|||
627 | this._changing(this.id + '-lhs', this.id + '-rhs'); |
|
|||
628 | this._set_top_offset(this.id + '-lhs'); |
|
|||
629 | }, |
|
|||
630 | diff: function() { |
|
|||
631 | var lhs = this.editor[this.id + '-lhs'].getValue(); |
|
|||
632 | var rhs = this.editor[this.id + '-rhs'].getValue(); |
|
|||
633 | var d = new Mgly.diff(lhs, rhs, this.settings); |
|
|||
634 | return d.normal_form(); |
|
|||
635 | }, |
|
|||
636 | bind: function(el) { |
|
|||
637 | this.element.hide();//hide |
|
|||
638 | this.id = jQuery(el).attr('id'); |
|
|||
639 | this.changed_timeout = null; |
|
|||
640 | this.chfns = {}; |
|
|||
641 | this.chfns[this.id + '-lhs'] = []; |
|
|||
642 | this.chfns[this.id + '-rhs'] = []; |
|
|||
643 | this.prev_query = []; |
|
|||
644 | this.cursor = []; |
|
|||
645 | this._skipscroll = {}; |
|
|||
646 | this.change_exp = new RegExp(/(\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/); |
|
|||
647 | var merge_lhs_button; |
|
|||
648 | var merge_rhs_button; |
|
|||
649 | if (jQuery.button != undefined) { |
|
|||
650 | //jquery ui |
|
|||
651 | merge_lhs_button = '<button title="Merge left"></button>'; |
|
|||
652 | merge_rhs_button = '<button title="Merge right"></button>'; |
|
|||
653 | } |
|
|||
654 | else { |
|
|||
655 | // homebrew |
|
|||
656 | var style = 'opacity:0.4;width:10px;height:15px;background-color:#888;cursor:pointer;text-align:center;color:#eee;border:1px solid: #222;margin-right:5px;margin-top: -2px;'; |
|
|||
657 | merge_lhs_button = '<div style="' + style + '" title="Merge left"><</div>'; |
|
|||
658 | merge_rhs_button = '<div style="' + style + '" title="Merge right">></div>'; |
|
|||
659 | } |
|
|||
660 | this.merge_rhs_button = jQuery(merge_rhs_button); |
|
|||
661 | this.merge_lhs_button = jQuery(merge_lhs_button); |
|
|||
662 |
|
||||
663 | // create the textarea and canvas elements |
|
|||
664 | var height = this.settings.editor_height; |
|
|||
665 | var width = this.settings.editor_width; |
|
|||
666 | this.element.append(jQuery('<div class="mergely-margin" style="height: ' + height + '"><canvas id="' + this.id + '-lhs-margin" width="8px" height="' + height + '"></canvas></div>')); |
|
|||
667 | this.element.append(jQuery('<div style="position:relative;width:' + width + '; height:' + height + '" id="' + this.id + '-editor-lhs" class="mergely-column"><textarea style="" id="' + this.id + '-lhs"></textarea></div>')); |
|
|||
668 | this.element.append(jQuery('<div class="mergely-canvas" style="height: ' + height + '"><canvas id="' + this.id + '-lhs-' + this.id + '-rhs-canvas" style="width:28px" width="28px" height="' + height + '"></canvas></div>')); |
|
|||
669 | var rmargin = jQuery('<div class="mergely-margin" style="height: ' + height + '"><canvas id="' + this.id + '-rhs-margin" width="8px" height="' + height + '"></canvas></div>'); |
|
|||
670 | if (!this.settings.sidebar) { |
|
|||
671 | this.element.find('.mergely-margin').css({display: 'none'}); |
|
|||
672 | } |
|
|||
673 | if (this.settings.rhs_margin == 'left') { |
|
|||
674 | this.element.append(rmargin); |
|
|||
675 | } |
|
|||
676 | this.element.append(jQuery('<div style="width:' + width + '; height:' + height + '" id="' + this.id + '-editor-rhs" class="mergely-column"><textarea style="" id="' + this.id + '-rhs"></textarea></div>')); |
|
|||
677 | if (this.settings.rhs_margin != 'left') { |
|
|||
678 | this.element.append(rmargin); |
|
|||
679 | } |
|
|||
680 | //codemirror |
|
|||
681 | var cmstyle = '#' + this.id + ' .CodeMirror-gutter-text { padding: 5px 0 0 0; }' + |
|
|||
682 | '#' + this.id + ' .CodeMirror-lines pre, ' + '#' + this.id + ' .CodeMirror-gutter-text pre { line-height: 18px; }' + |
|
|||
683 | '.CodeMirror-linewidget { overflow: hidden; };'; |
|
|||
684 | if (this.settings.autoresize) { |
|
|||
685 | cmstyle += this.id + ' .CodeMirror-scroll { height: 100%; overflow: auto; }'; |
|
|||
686 | } |
|
|||
687 | // adjust the margin line height |
|
|||
688 | cmstyle += '\n.CodeMirror { line-height: 18px; }'; |
|
|||
689 | jQuery('<style type="text/css">' + cmstyle + '</style>').appendTo('head'); |
|
|||
690 |
|
||||
691 | //bind |
|
|||
692 | var rhstx = this.element.find('#' + this.id + '-rhs').get(0); |
|
|||
693 | if (!rhstx) { |
|
|||
694 | console.error('rhs textarea not defined - Mergely not initialized properly'); |
|
|||
695 | return; |
|
|||
696 | } |
|
|||
697 | var lhstx = this.element.find('#' + this.id + '-lhs').get(0); |
|
|||
698 | if (!rhstx) { |
|
|||
699 | console.error('lhs textarea not defined - Mergely not initialized properly'); |
|
|||
700 | return; |
|
|||
701 | } |
|
|||
702 | var self = this; |
|
|||
703 | this.editor = []; |
|
|||
704 | this.editor[this.id + '-lhs'] = CodeMirror.fromTextArea(lhstx, this.lhs_cmsettings); |
|
|||
705 | this.editor[this.id + '-rhs'] = CodeMirror.fromTextArea(rhstx, this.rhs_cmsettings); |
|
|||
706 | this.editor[this.id + '-lhs'].on('change', function(){ if (self.settings.autoupdate) self._changing(self.id + '-lhs', self.id + '-rhs'); }); |
|
|||
707 | this.editor[this.id + '-lhs'].on('scroll', function(){ self._scrolling(self.id + '-lhs'); }); |
|
|||
708 | this.editor[this.id + '-rhs'].on('change', function(){ if (self.settings.autoupdate) self._changing(self.id + '-lhs', self.id + '-rhs'); }); |
|
|||
709 | this.editor[this.id + '-rhs'].on('scroll', function(){ self._scrolling(self.id + '-rhs'); }); |
|
|||
710 | // resize |
|
|||
711 | if (this.settings.autoresize) { |
|
|||
712 | var sz_timeout1 = null; |
|
|||
713 | var sz = function(init) { |
|
|||
714 | //self.em_height = null; //recalculate |
|
|||
715 | if (self.settings.resize) self.settings.resize(init); |
|
|||
716 | self.editor[self.id + '-lhs'].refresh(); |
|
|||
717 | self.editor[self.id + '-rhs'].refresh(); |
|
|||
718 | if (self.settings.autoupdate) { |
|
|||
719 | self._changing(self.id + '-lhs', self.id + '-rhs'); |
|
|||
720 | } |
|
|||
721 | }; |
|
|||
722 | jQuery(window).on('resize.mergely', |
|
|||
723 | function () { |
|
|||
724 | if (sz_timeout1) clearTimeout(sz_timeout1); |
|
|||
725 | sz_timeout1 = setTimeout(sz, self.settings.resize_timeout); |
|
|||
726 | } |
|
|||
727 | ); |
|
|||
728 | sz(true); |
|
|||
729 | } |
|
|||
730 |
|
||||
731 | // scrollToDiff() from gutter |
|
|||
732 | function gutterClicked(side, line, ev) { |
|
|||
733 | // The "Merge left/right" buttons are also located in the gutter. |
|
|||
734 | // Don't interfere with them: |
|
|||
735 | if (ev.target && (jQuery(ev.target).closest('.merge-button').length > 0)) { |
|
|||
736 | return; |
|
|||
737 | } |
|
|||
738 |
|
||||
739 | // See if the user clicked the line number of a difference: |
|
|||
740 | var i, change; |
|
|||
741 | for (i = 0; i < this.changes.length; i++) { |
|
|||
742 | change = this.changes[i]; |
|
|||
743 | if (line >= change[side+'-line-from'] && line <= change[side+'-line-to']) { |
|
|||
744 | this._current_diff = i; |
|
|||
745 | // I really don't like this here - something about gutterClick does not |
|
|||
746 | // like mutating editor here. Need to trigger the scroll to diff from |
|
|||
747 | // a timeout. |
|
|||
748 | setTimeout(function() { this.scrollToDiff(); }.bind(this), 10); |
|
|||
749 | break; |
|
|||
750 | } |
|
|||
751 | } |
|
|||
752 | } |
|
|||
753 |
|
||||
754 | this.editor[this.id + '-lhs'].on('gutterClick', function(cm, n, gutterClass, ev) { |
|
|||
755 | gutterClicked.call(this, 'lhs', n, ev); |
|
|||
756 | }.bind(this)); |
|
|||
757 |
|
||||
758 | this.editor[this.id + '-rhs'].on('gutterClick', function(cm, n, gutterClass, ev) { |
|
|||
759 | gutterClicked.call(this, 'rhs', n, ev); |
|
|||
760 | }.bind(this)); |
|
|||
761 |
|
||||
762 | //bind |
|
|||
763 | var setv; |
|
|||
764 | if (this.settings.lhs) { |
|
|||
765 | setv = this.editor[this.id + '-lhs'].getDoc().setValue; |
|
|||
766 | this.settings.lhs(setv.bind(this.editor[this.id + '-lhs'].getDoc())); |
|
|||
767 | } |
|
|||
768 | if (this.settings.rhs) { |
|
|||
769 | setv = this.editor[this.id + '-rhs'].getDoc().setValue; |
|
|||
770 | this.settings.rhs(setv.bind(this.editor[this.id + '-rhs'].getDoc())); |
|
|||
771 | } |
|
|||
772 | }, |
|
|||
773 |
|
||||
774 | _scroll_to_change : function(change) { |
|
|||
775 | if (!change) return; |
|
|||
776 | var self = this; |
|
|||
777 | var led = self.editor[self.id+'-lhs']; |
|
|||
778 | var red = self.editor[self.id+'-rhs']; |
|
|||
779 | // set cursors |
|
|||
780 | led.setCursor(Math.max(change["lhs-line-from"],0), 0); // use led.getCursor().ch ? |
|
|||
781 | red.setCursor(Math.max(change["rhs-line-from"],0), 0); |
|
|||
782 | led.scrollIntoView({line: change["lhs-line-to"]}); |
|
|||
783 | }, |
|
|||
784 |
|
||||
785 | _scrolling: function(editor_name) { |
|
|||
786 | if (this._skipscroll[editor_name] === true) { |
|
|||
787 | // scrolling one side causes the other to event - ignore it |
|
|||
788 | this._skipscroll[editor_name] = false; |
|
|||
789 | return; |
|
|||
790 | } |
|
|||
791 | var scroller = jQuery(this.editor[editor_name].getScrollerElement()); |
|
|||
792 | if (this.midway == undefined) { |
|
|||
793 | this.midway = (scroller.height() / 2.0 + scroller.offset().top).toFixed(2); |
|
|||
794 | } |
|
|||
795 | // balance-line |
|
|||
796 | var midline = this.editor[editor_name].coordsChar({left:0, top:this.midway}); |
|
|||
797 | var top_to = scroller.scrollTop(); |
|
|||
798 | var left_to = scroller.scrollLeft(); |
|
|||
799 |
|
||||
800 | this.trace('scroll', 'side', editor_name); |
|
|||
801 | this.trace('scroll', 'midway', this.midway); |
|
|||
802 | this.trace('scroll', 'midline', midline); |
|
|||
803 | this.trace('scroll', 'top_to', top_to); |
|
|||
804 | this.trace('scroll', 'left_to', left_to); |
|
|||
805 |
|
||||
806 | var editor_name1 = this.id + '-lhs'; |
|
|||
807 | var editor_name2 = this.id + '-rhs'; |
|
|||
808 |
|
||||
809 | for (var name in this.editor) { |
|
|||
810 | if (!this.editor.hasOwnProperty(name)) continue; |
|
|||
811 | if (editor_name == name) continue; //same editor |
|
|||
812 | var this_side = editor_name.replace(this.id + '-', ''); |
|
|||
813 | var other_side = name.replace(this.id + '-', ''); |
|
|||
814 | var top_adjust = 0; |
|
|||
815 |
|
||||
816 | // find the last change that is less than or within the midway point |
|
|||
817 | // do not move the rhs until the lhs end point is >= the rhs end point. |
|
|||
818 | var last_change = null; |
|
|||
819 | var force_scroll = false; |
|
|||
820 | for (var i = 0; i < this.changes.length; ++i) { |
|
|||
821 | var change = this.changes[i]; |
|
|||
822 | if ((midline.line >= change[this_side+'-line-from'])) { |
|
|||
823 | last_change = change; |
|
|||
824 | if (midline.line >= last_change[this_side+'-line-to']) { |
|
|||
825 | if (!change.hasOwnProperty(this_side+'-y-start') || |
|
|||
826 | !change.hasOwnProperty(this_side+'-y-end') || |
|
|||
827 | !change.hasOwnProperty(other_side+'-y-start') || |
|
|||
828 | !change.hasOwnProperty(other_side+'-y-end')){ |
|
|||
829 | // change outside of viewport |
|
|||
830 | force_scroll = true; |
|
|||
831 | } |
|
|||
832 | else { |
|
|||
833 | top_adjust += |
|
|||
834 | (change[this_side+'-y-end'] - change[this_side+'-y-start']) - |
|
|||
835 | (change[other_side+'-y-end'] - change[other_side+'-y-start']); |
|
|||
836 | } |
|
|||
837 | } |
|
|||
838 | } |
|
|||
839 | } |
|
|||
840 |
|
||||
841 | var vp = this.editor[name].getViewport(); |
|
|||
842 | var scroll = true; |
|
|||
843 | if (last_change) { |
|
|||
844 | this.trace('scroll', 'last change before midline', last_change); |
|
|||
845 | if (midline.line >= vp.from && midline <= vp.to) { |
|
|||
846 | scroll = false; |
|
|||
847 | } |
|
|||
848 | } |
|
|||
849 | this.trace('scroll', 'scroll', scroll); |
|
|||
850 | if (scroll || force_scroll) { |
|
|||
851 | // scroll the other side |
|
|||
852 | this.trace('scroll', 'scrolling other side', top_to - top_adjust); |
|
|||
853 | this._skipscroll[name] = true;//disable next event |
|
|||
854 | this.editor[name].scrollTo(left_to, top_to - top_adjust); |
|
|||
855 | } |
|
|||
856 | else this.trace('scroll', 'not scrolling other side'); |
|
|||
857 |
|
||||
858 | if (this.settings.autoupdate) { |
|
|||
859 | var timer = new Mgly.Timer(); |
|
|||
860 | this._calculate_offsets(editor_name1, editor_name2, this.changes); |
|
|||
861 | this.trace('change', 'offsets time', timer.stop()); |
|
|||
862 | this._markup_changes(editor_name1, editor_name2, this.changes); |
|
|||
863 | this.trace('change', 'markup time', timer.stop()); |
|
|||
864 | this._draw_diff(editor_name1, editor_name2, this.changes); |
|
|||
865 | this.trace('change', 'draw time', timer.stop()); |
|
|||
866 | } |
|
|||
867 | this.trace('scroll', 'scrolled'); |
|
|||
868 | } |
|
|||
869 | }, |
|
|||
870 | _changing: function(editor_name1, editor_name2) { |
|
|||
871 | this.trace('change', 'changing-timeout', this.changed_timeout); |
|
|||
872 | var self = this; |
|
|||
873 | if (this.changed_timeout != null) clearTimeout(this.changed_timeout); |
|
|||
874 | this.changed_timeout = setTimeout(function(){ |
|
|||
875 | var timer = new Mgly.Timer(); |
|
|||
876 | self._changed(editor_name1, editor_name2); |
|
|||
877 | self.trace('change', 'total time', timer.stop()); |
|
|||
878 | }, this.settings.change_timeout); |
|
|||
879 | }, |
|
|||
880 | _changed: function(editor_name1, editor_name2) { |
|
|||
881 | this._clear(); |
|
|||
882 | this._diff(editor_name1, editor_name2); |
|
|||
883 | }, |
|
|||
884 | _clear: function() { |
|
|||
885 | var self = this, name, editor, fns, timer, i, change, l; |
|
|||
886 |
|
||||
887 | var clear_changes = function() { |
|
|||
888 | timer = new Mgly.Timer(); |
|
|||
889 | for (i = 0, l = editor.lineCount(); i < l; ++i) { |
|
|||
890 | editor.removeLineClass(i, 'background'); |
|
|||
891 | } |
|
|||
892 | for (i = 0; i < fns.length; ++i) { |
|
|||
893 | //var edid = editor.getDoc().id; |
|
|||
894 | change = fns[i]; |
|
|||
895 | //if (change.doc.id != edid) continue; |
|
|||
896 | if (change.lines.length) { |
|
|||
897 | self.trace('change', 'clear text', change.lines[0].text); |
|
|||
898 | } |
|
|||
899 | change.clear(); |
|
|||
900 | } |
|
|||
901 | editor.clearGutter('merge'); |
|
|||
902 | self.trace('change', 'clear time', timer.stop()); |
|
|||
903 | }; |
|
|||
904 |
|
||||
905 | for (name in this.editor) { |
|
|||
906 | if (!this.editor.hasOwnProperty(name)) continue; |
|
|||
907 | editor = this.editor[name]; |
|
|||
908 | fns = self.chfns[name]; |
|
|||
909 | // clear editor changes |
|
|||
910 | editor.operation(clear_changes); |
|
|||
911 | } |
|
|||
912 | self.chfns[name] = []; |
|
|||
913 |
|
||||
914 | var ex = this._draw_info(this.id + '-lhs', this.id + '-rhs'); |
|
|||
915 | var ctx_lhs = ex.clhs.get(0).getContext('2d'); |
|
|||
916 | var ctx_rhs = ex.crhs.get(0).getContext('2d'); |
|
|||
917 | var ctx = ex.dcanvas.getContext('2d'); |
|
|||
918 |
|
||||
919 | ctx_lhs.beginPath(); |
|
|||
920 | ctx_lhs.fillStyle = this.settings.bgcolor; |
|
|||
921 | ctx_lhs.strokeStyle = '#888'; |
|
|||
922 | ctx_lhs.fillRect(0, 0, 6.5, ex.visible_page_height); |
|
|||
923 | ctx_lhs.strokeRect(0, 0, 6.5, ex.visible_page_height); |
|
|||
924 |
|
||||
925 | ctx_rhs.beginPath(); |
|
|||
926 | ctx_rhs.fillStyle = this.settings.bgcolor; |
|
|||
927 | ctx_rhs.strokeStyle = '#888'; |
|
|||
928 | ctx_rhs.fillRect(0, 0, 6.5, ex.visible_page_height); |
|
|||
929 | ctx_rhs.strokeRect(0, 0, 6.5, ex.visible_page_height); |
|
|||
930 |
|
||||
931 | ctx.beginPath(); |
|
|||
932 | ctx.fillStyle = '#fff'; |
|
|||
933 | ctx.fillRect(0, 0, this.draw_mid_width, ex.visible_page_height); |
|
|||
934 | }, |
|
|||
935 | _diff: function(editor_name1, editor_name2) { |
|
|||
936 | var lhs = this.editor[editor_name1].getValue(); |
|
|||
937 | var rhs = this.editor[editor_name2].getValue(); |
|
|||
938 | var timer = new Mgly.Timer(); |
|
|||
939 | var d = new Mgly.diff(lhs, rhs, this.settings); |
|
|||
940 | this.trace('change', 'diff time', timer.stop()); |
|
|||
941 | this.changes = Mgly.DiffParser(d.normal_form()); |
|
|||
942 | this.trace('change', 'parse time', timer.stop()); |
|
|||
943 | if (this._current_diff === undefined && this.changes.length) { |
|
|||
944 | // go to first difference on start-up |
|
|||
945 | this._current_diff = 0; |
|
|||
946 | this._scroll_to_change(this.changes[0]); |
|
|||
947 | } |
|
|||
948 | this.trace('change', 'scroll_to_change time', timer.stop()); |
|
|||
949 | this._calculate_offsets(editor_name1, editor_name2, this.changes); |
|
|||
950 | this.trace('change', 'offsets time', timer.stop()); |
|
|||
951 | this._markup_changes(editor_name1, editor_name2, this.changes); |
|
|||
952 | this.trace('change', 'markup time', timer.stop()); |
|
|||
953 | this._draw_diff(editor_name1, editor_name2, this.changes); |
|
|||
954 | this.trace('change', 'draw time', timer.stop()); |
|
|||
955 | }, |
|
|||
956 | _parse_diff: function (editor_name1, editor_name2, diff) { |
|
|||
957 | this.trace('diff', 'diff results:\n', diff); |
|
|||
958 | var changes = []; |
|
|||
959 | var change_id = 0; |
|
|||
960 | // parse diff |
|
|||
961 | var diff_lines = diff.split(/\n/); |
|
|||
962 | for (var i = 0; i < diff_lines.length; ++i) { |
|
|||
963 | if (diff_lines[i].length == 0) continue; |
|
|||
964 | var change = {}; |
|
|||
965 | var test = this.change_exp.exec(diff_lines[i]); |
|
|||
966 | if (test == null) continue; |
|
|||
967 | // lines are zero-based |
|
|||
968 | var fr = test[1].split(','); |
|
|||
969 | change['lhs-line-from'] = fr[0] - 1; |
|
|||
970 | if (fr.length == 1) change['lhs-line-to'] = fr[0] - 1; |
|
|||
971 | else change['lhs-line-to'] = fr[1] - 1; |
|
|||
972 | var to = test[3].split(','); |
|
|||
973 | change['rhs-line-from'] = to[0] - 1; |
|
|||
974 | if (to.length == 1) change['rhs-line-to'] = to[0] - 1; |
|
|||
975 | else change['rhs-line-to'] = to[1] - 1; |
|
|||
976 | // TODO: optimize for changes that are adds/removes |
|
|||
977 | if (change['lhs-line-from'] < 0) change['lhs-line-from'] = 0; |
|
|||
978 | if (change['lhs-line-to'] < 0) change['lhs-line-to'] = 0; |
|
|||
979 | if (change['rhs-line-from'] < 0) change['rhs-line-from'] = 0; |
|
|||
980 | if (change['rhs-line-to'] < 0) change['rhs-line-to'] = 0; |
|
|||
981 | change['op'] = test[2]; |
|
|||
982 | changes[change_id++] = change; |
|
|||
983 | this.trace('diff', 'change', change); |
|
|||
984 | } |
|
|||
985 | return changes; |
|
|||
986 | }, |
|
|||
987 | _get_viewport: function(editor_name1, editor_name2) { |
|
|||
988 | var lhsvp = this.editor[editor_name1].getViewport(); |
|
|||
989 | var rhsvp = this.editor[editor_name2].getViewport(); |
|
|||
990 | return {from: Math.min(lhsvp.from, rhsvp.from), to: Math.max(lhsvp.to, rhsvp.to)}; |
|
|||
991 | }, |
|
|||
992 | _is_change_in_view: function(vp, change) { |
|
|||
993 | if (!this.settings.viewport) return true; |
|
|||
994 | if ((change['lhs-line-from'] < vp.from && change['lhs-line-to'] < vp.to) || |
|
|||
995 | (change['lhs-line-from'] > vp.from && change['lhs-line-to'] > vp.to) || |
|
|||
996 | (change['rhs-line-from'] < vp.from && change['rhs-line-to'] < vp.to) || |
|
|||
997 | (change['rhs-line-from'] > vp.from && change['rhs-line-to'] > vp.to)) { |
|
|||
998 | // if the change is outside the viewport, skip |
|
|||
999 | return false; |
|
|||
1000 | } |
|
|||
1001 | return true; |
|
|||
1002 | }, |
|
|||
1003 | _set_top_offset: function (editor_name1) { |
|
|||
1004 | // save the current scroll position of the editor |
|
|||
1005 | var saveY = this.editor[editor_name1].getScrollInfo().top; |
|
|||
1006 | // temporarily scroll to top |
|
|||
1007 | this.editor[editor_name1].scrollTo(null, 0); |
|
|||
1008 |
|
||||
1009 | // this is the distance from the top of the screen to the top of the |
|
|||
1010 | // content of the first codemirror editor |
|
|||
1011 | var topnode = this.element.find('.CodeMirror-measure').first(); |
|
|||
1012 | var top_offset = topnode.offset().top - 4; |
|
|||
1013 | if(!top_offset) return false; |
|
|||
1014 |
|
||||
1015 | // restore editor's scroll position |
|
|||
1016 | this.editor[editor_name1].scrollTo(null, saveY); |
|
|||
1017 |
|
||||
1018 | this.draw_top_offset = 0.5 - top_offset; |
|
|||
1019 | return true; |
|
|||
1020 | }, |
|
|||
1021 | _calculate_offsets: function (editor_name1, editor_name2, changes) { |
|
|||
1022 | if (this.em_height == null) { |
|
|||
1023 | if(!this._set_top_offset(editor_name1)) return; //try again |
|
|||
1024 | this.em_height = this.editor[editor_name1].defaultTextHeight(); |
|
|||
1025 | if (!this.em_height) { |
|
|||
1026 | console.warn('Failed to calculate offsets, using 18 by default'); |
|
|||
1027 | this.em_height = 18; |
|
|||
1028 | } |
|
|||
1029 | this.draw_lhs_min = 0.5; |
|
|||
1030 | var c = jQuery('#' + editor_name1 + '-' + editor_name2 + '-canvas'); |
|
|||
1031 | if (!c.length) { |
|
|||
1032 | console.error('failed to find canvas', '#' + editor_name1 + '-' + editor_name2 + '-canvas'); |
|
|||
1033 | } |
|
|||
1034 | if (!c.width()) { |
|
|||
1035 | console.error('canvas width is 0'); |
|
|||
1036 | return; |
|
|||
1037 | } |
|
|||
1038 | this.draw_mid_width = jQuery('#' + editor_name1 + '-' + editor_name2 + '-canvas').width(); |
|
|||
1039 | this.draw_rhs_max = this.draw_mid_width - 0.5; //24.5; |
|
|||
1040 | this.draw_lhs_width = 5; |
|
|||
1041 | this.draw_rhs_width = 5; |
|
|||
1042 | this.trace('calc', 'change offsets calculated', {top_offset: this.draw_top_offset, lhs_min: this.draw_lhs_min, rhs_max: this.draw_rhs_max, lhs_width: this.draw_lhs_width, rhs_width: this.draw_rhs_width}); |
|
|||
1043 | } |
|
|||
1044 | var lhschc = this.editor[editor_name1].charCoords({line: 0}); |
|
|||
1045 | var rhschc = this.editor[editor_name2].charCoords({line: 0}); |
|
|||
1046 | var vp = this._get_viewport(editor_name1, editor_name2); |
|
|||
1047 |
|
||||
1048 | for (var i = 0; i < changes.length; ++i) { |
|
|||
1049 | var change = changes[i]; |
|
|||
1050 |
|
||||
1051 | if (!this.settings.sidebar && !this._is_change_in_view(vp, change)) { |
|
|||
1052 | // if the change is outside the viewport, skip |
|
|||
1053 | delete change['lhs-y-start']; |
|
|||
1054 | delete change['lhs-y-end']; |
|
|||
1055 | delete change['rhs-y-start']; |
|
|||
1056 | delete change['rhs-y-end']; |
|
|||
1057 | continue; |
|
|||
1058 | } |
|
|||
1059 | var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0; |
|
|||
1060 | var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0; |
|
|||
1061 | var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0; |
|
|||
1062 | var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0; |
|
|||
1063 |
|
||||
1064 | var ls, le, rs, re, tls, tle, lhseh, lhssh, rhssh, rhseh; |
|
|||
1065 | if (this.editor[editor_name1].getOption('lineWrapping') || this.editor[editor_name2].getOption('lineWrapping')) { |
|
|||
1066 | // If using line-wrapping, we must get the height of the line |
|
|||
1067 | tls = this.editor[editor_name1].cursorCoords({line: llf, ch: 0}, 'page'); |
|
|||
1068 | lhssh = this.editor[editor_name1].getLineHandle(llf); |
|
|||
1069 | ls = { top: tls.top, bottom: tls.top + lhssh.height }; |
|
|||
1070 |
|
||||
1071 | tle = this.editor[editor_name1].cursorCoords({line: llt, ch: 0}, 'page'); |
|
|||
1072 | lhseh = this.editor[editor_name1].getLineHandle(llt); |
|
|||
1073 | le = { top: tle.top, bottom: tle.top + lhseh.height }; |
|
|||
1074 |
|
||||
1075 | tls = this.editor[editor_name2].cursorCoords({line: rlf, ch: 0}, 'page'); |
|
|||
1076 | rhssh = this.editor[editor_name2].getLineHandle(rlf); |
|
|||
1077 | rs = { top: tls.top, bottom: tls.top + rhssh.height }; |
|
|||
1078 |
|
||||
1079 | tle = this.editor[editor_name2].cursorCoords({line: rlt, ch: 0}, 'page'); |
|
|||
1080 | rhseh = this.editor[editor_name2].getLineHandle(rlt); |
|
|||
1081 | re = { top: tle.top, bottom: tle.top + rhseh.height }; |
|
|||
1082 | } |
|
|||
1083 | else { |
|
|||
1084 | // If not using line-wrapping, we can calculate the line position |
|
|||
1085 | ls = { |
|
|||
1086 | top: lhschc.top + llf * this.em_height, |
|
|||
1087 | bottom: lhschc.bottom + llf * this.em_height + 2 |
|
|||
1088 | }; |
|
|||
1089 | le = { |
|
|||
1090 | top: lhschc.top + llt * this.em_height, |
|
|||
1091 | bottom: lhschc.bottom + llt * this.em_height + 2 |
|
|||
1092 | }; |
|
|||
1093 | rs = { |
|
|||
1094 | top: rhschc.top + rlf * this.em_height, |
|
|||
1095 | bottom: rhschc.bottom + rlf * this.em_height + 2 |
|
|||
1096 | }; |
|
|||
1097 | re = { |
|
|||
1098 | top: rhschc.top + rlt * this.em_height, |
|
|||
1099 | bottom: rhschc.bottom + rlt * this.em_height + 2 |
|
|||
1100 | }; |
|
|||
1101 | } |
|
|||
1102 |
|
||||
1103 | if (change['op'] == 'a') { |
|
|||
1104 | // adds (right), normally start from the end of the lhs, |
|
|||
1105 | // except for the case when the start of the rhs is 0 |
|
|||
1106 | if (rlf > 0) { |
|
|||
1107 | ls.top = ls.bottom; |
|
|||
1108 | ls.bottom += this.em_height; |
|
|||
1109 | le = ls; |
|
|||
1110 | } |
|
|||
1111 | } |
|
|||
1112 | else if (change['op'] == 'd') { |
|
|||
1113 | // deletes (left) normally finish from the end of the rhs, |
|
|||
1114 | // except for the case when the start of the lhs is 0 |
|
|||
1115 | if (llf > 0) { |
|
|||
1116 | rs.top = rs.bottom; |
|
|||
1117 | rs.bottom += this.em_height; |
|
|||
1118 | re = rs; |
|
|||
1119 | } |
|
|||
1120 | } |
|
|||
1121 | change['lhs-y-start'] = this.draw_top_offset + ls.top; |
|
|||
1122 | if (change['op'] == 'c' || change['op'] == 'd') { |
|
|||
1123 | change['lhs-y-end'] = this.draw_top_offset + le.bottom; |
|
|||
1124 | } |
|
|||
1125 | else { |
|
|||
1126 | change['lhs-y-end'] = this.draw_top_offset + le.top; |
|
|||
1127 | } |
|
|||
1128 | change['rhs-y-start'] = this.draw_top_offset + rs.top; |
|
|||
1129 | if (change['op'] == 'c' || change['op'] == 'a') { |
|
|||
1130 | change['rhs-y-end'] = this.draw_top_offset + re.bottom; |
|
|||
1131 | } |
|
|||
1132 | else { |
|
|||
1133 | change['rhs-y-end'] = this.draw_top_offset + re.top; |
|
|||
1134 | } |
|
|||
1135 | this.trace('calc', 'change calculated', i, change); |
|
|||
1136 | } |
|
|||
1137 | return changes; |
|
|||
1138 | }, |
|
|||
1139 | _markup_changes: function (editor_name1, editor_name2, changes) { |
|
|||
1140 | this.element.find('.merge-button').remove(); //clear |
|
|||
1141 |
|
||||
1142 | var self = this; |
|
|||
1143 | var led = this.editor[editor_name1]; |
|
|||
1144 | var red = this.editor[editor_name2]; |
|
|||
1145 | var current_diff = this._current_diff; |
|
|||
1146 |
|
||||
1147 | var timer = new Mgly.Timer(); |
|
|||
1148 | led.operation(function() { |
|
|||
1149 | for (var i = 0; i < changes.length; ++i) { |
|
|||
1150 | var change = changes[i]; |
|
|||
1151 | var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0; |
|
|||
1152 | var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0; |
|
|||
1153 | var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0; |
|
|||
1154 | var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0; |
|
|||
1155 |
|
||||
1156 | var clazz = ['mergely', 'lhs', change['op'], 'cid-' + i]; |
|
|||
1157 | led.addLineClass(llf, 'background', 'start'); |
|
|||
1158 | led.addLineClass(llt, 'background', 'end'); |
|
|||
1159 |
|
||||
1160 | if (current_diff == i) { |
|
|||
1161 | if (llf != llt) { |
|
|||
1162 | led.addLineClass(llf, 'background', 'current'); |
|
|||
1163 | } |
|
|||
1164 | led.addLineClass(llt, 'background', 'current'); |
|
|||
1165 | } |
|
|||
1166 | if (llf == 0 && llt == 0 && rlf == 0) { |
|
|||
1167 | led.addLineClass(llf, 'background', clazz.join(' ')); |
|
|||
1168 | led.addLineClass(llf, 'background', 'first'); |
|
|||
1169 | } |
|
|||
1170 | else { |
|
|||
1171 | // apply change for each line in-between the changed lines |
|
|||
1172 | for (var j = llf; j <= llt; ++j) { |
|
|||
1173 | led.addLineClass(j, 'background', clazz.join(' ')); |
|
|||
1174 | led.addLineClass(j, 'background', clazz.join(' ')); |
|
|||
1175 | } |
|
|||
1176 | } |
|
|||
1177 |
|
||||
1178 | if (!red.getOption('readOnly')) { |
|
|||
1179 | // add widgets to lhs, if rhs is not read only |
|
|||
1180 | var rhs_button = self.merge_rhs_button.clone(); |
|
|||
1181 | if (rhs_button.button) { |
|
|||
1182 | //jquery-ui support |
|
|||
1183 | rhs_button.button({icons: {primary: 'ui-icon-triangle-1-e'}, text: false}); |
|
|||
1184 | } |
|
|||
1185 | rhs_button.addClass('merge-button'); |
|
|||
1186 | rhs_button.attr('id', 'merge-rhs-' + i); |
|
|||
1187 | led.setGutterMarker(llf, 'merge', rhs_button.get(0)); |
|
|||
1188 | } |
|
|||
1189 | } |
|
|||
1190 | }); |
|
|||
1191 |
|
||||
1192 | var vp = this._get_viewport(editor_name1, editor_name2); |
|
|||
1193 |
|
||||
1194 | this.trace('change', 'markup lhs-editor time', timer.stop()); |
|
|||
1195 | red.operation(function() { |
|
|||
1196 | for (var i = 0; i < changes.length; ++i) { |
|
|||
1197 | var change = changes[i]; |
|
|||
1198 | var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0; |
|
|||
1199 | var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0; |
|
|||
1200 | var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0; |
|
|||
1201 | var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0; |
|
|||
1202 |
|
||||
1203 | if (!self._is_change_in_view(vp, change)) { |
|
|||
1204 | // if the change is outside the viewport, skip |
|
|||
1205 | continue; |
|
|||
1206 | } |
|
|||
1207 |
|
||||
1208 | var clazz = ['mergely', 'rhs', change['op'], 'cid-' + i]; |
|
|||
1209 | red.addLineClass(rlf, 'background', 'start'); |
|
|||
1210 | red.addLineClass(rlt, 'background', 'end'); |
|
|||
1211 |
|
||||
1212 | if (current_diff == i) { |
|
|||
1213 | if (rlf != rlt) { |
|
|||
1214 | red.addLineClass(rlf, 'background', 'current'); |
|
|||
1215 | } |
|
|||
1216 | red.addLineClass(rlt, 'background', 'current'); |
|
|||
1217 | } |
|
|||
1218 | if (rlf == 0 && rlt == 0 && llf == 0) { |
|
|||
1219 | red.addLineClass(rlf, 'background', clazz.join(' ')); |
|
|||
1220 | red.addLineClass(rlf, 'background', 'first'); |
|
|||
1221 | } |
|
|||
1222 | else { |
|
|||
1223 | // apply change for each line in-between the changed lines |
|
|||
1224 | for (var j = rlf; j <= rlt; ++j) { |
|
|||
1225 | red.addLineClass(j, 'background', clazz.join(' ')); |
|
|||
1226 | red.addLineClass(j, 'background', clazz.join(' ')); |
|
|||
1227 | } |
|
|||
1228 | } |
|
|||
1229 |
|
||||
1230 | if (!led.getOption('readOnly')) { |
|
|||
1231 | // add widgets to rhs, if lhs is not read only |
|
|||
1232 | var lhs_button = self.merge_lhs_button.clone(); |
|
|||
1233 | if (lhs_button.button) { |
|
|||
1234 | //jquery-ui support |
|
|||
1235 | lhs_button.button({icons: {primary: 'ui-icon-triangle-1-w'}, text: false}); |
|
|||
1236 | } |
|
|||
1237 | lhs_button.addClass('merge-button'); |
|
|||
1238 | lhs_button.attr('id', 'merge-lhs-' + i); |
|
|||
1239 | red.setGutterMarker(rlf, 'merge', lhs_button.get(0)); |
|
|||
1240 | } |
|
|||
1241 | } |
|
|||
1242 | }); |
|
|||
1243 | this.trace('change', 'markup rhs-editor time', timer.stop()); |
|
|||
1244 |
|
||||
1245 | // mark text deleted, LCS changes |
|
|||
1246 | var marktext = [], i, j, k, p; |
|
|||
1247 | for (i = 0; this.settings.lcs && i < changes.length; ++i) { |
|
|||
1248 | var change = changes[i]; |
|
|||
1249 | var llf = change['lhs-line-from'] >= 0 ? change['lhs-line-from'] : 0; |
|
|||
1250 | var llt = change['lhs-line-to'] >= 0 ? change['lhs-line-to'] : 0; |
|
|||
1251 | var rlf = change['rhs-line-from'] >= 0 ? change['rhs-line-from'] : 0; |
|
|||
1252 | var rlt = change['rhs-line-to'] >= 0 ? change['rhs-line-to'] : 0; |
|
|||
1253 |
|
||||
1254 | if (!this._is_change_in_view(vp, change)) { |
|
|||
1255 | // if the change is outside the viewport, skip |
|
|||
1256 | continue; |
|
|||
1257 | } |
|
|||
1258 | if (change['op'] == 'd') { |
|
|||
1259 | // apply delete to cross-out (left-hand side only) |
|
|||
1260 | var from = llf; |
|
|||
1261 | var to = llt; |
|
|||
1262 | var to_ln = led.lineInfo(to); |
|
|||
1263 | if (to_ln) { |
|
|||
1264 | marktext.push([led, {line:from, ch:0}, {line:to, ch:to_ln.text.length}, {className: 'mergely ch d lhs'}]); |
|
|||
1265 | } |
|
|||
1266 | } |
|
|||
1267 | else if (change['op'] == 'c') { |
|
|||
1268 | // apply LCS changes to each line |
|
|||
1269 | for (j = llf, k = rlf, p = 0; |
|
|||
1270 | ((j >= 0) && (j <= llt)) || ((k >= 0) && (k <= rlt)); |
|
|||
1271 | ++j, ++k) { |
|
|||
1272 | var lhs_line, rhs_line; |
|
|||
1273 | if (k + p > rlt) { |
|
|||
1274 | // lhs continues past rhs, mark lhs as deleted |
|
|||
1275 | lhs_line = led.getLine( j ); |
|
|||
1276 | marktext.push([led, {line:j, ch:0}, {line:j, ch:lhs_line.length}, {className: 'mergely ch d lhs'}]); |
|
|||
1277 | continue; |
|
|||
1278 | } |
|
|||
1279 | if (j + p > llt) { |
|
|||
1280 | // rhs continues past lhs, mark rhs as added |
|
|||
1281 | rhs_line = red.getLine( k ); |
|
|||
1282 | marktext.push([red, {line:k, ch:0}, {line:k, ch:rhs_line.length}, {className: 'mergely ch a rhs'}]); |
|
|||
1283 | continue; |
|
|||
1284 | } |
|
|||
1285 | lhs_line = led.getLine( j ); |
|
|||
1286 | rhs_line = red.getLine( k ); |
|
|||
1287 | var lcs = new Mgly.LCS(lhs_line, rhs_line); |
|
|||
1288 | lcs.diff( |
|
|||
1289 | function added (from, to) { |
|
|||
1290 | marktext.push([red, {line:k, ch:from}, {line:k, ch:to}, {className: 'mergely ch a rhs'}]); |
|
|||
1291 | }, |
|
|||
1292 | function removed (from, to) { |
|
|||
1293 | marktext.push([led, {line:j, ch:from}, {line:j, ch:to}, {className: 'mergely ch d lhs'}]); |
|
|||
1294 | } |
|
|||
1295 | ); |
|
|||
1296 | } |
|
|||
1297 | } |
|
|||
1298 | } |
|
|||
1299 | this.trace('change', 'LCS marktext time', timer.stop()); |
|
|||
1300 |
|
||||
1301 | // mark changes outside closure |
|
|||
1302 | led.operation(function() { |
|
|||
1303 | // apply lhs markup |
|
|||
1304 | for (var i = 0; i < marktext.length; ++i) { |
|
|||
1305 | var m = marktext[i]; |
|
|||
1306 | if (m[0].doc.id != led.getDoc().id) continue; |
|
|||
1307 | self.chfns[self.id + '-lhs'].push(m[0].markText(m[1], m[2], m[3])); |
|
|||
1308 | } |
|
|||
1309 | }); |
|
|||
1310 | red.operation(function() { |
|
|||
1311 | // apply lhs markup |
|
|||
1312 | for (var i = 0; i < marktext.length; ++i) { |
|
|||
1313 | var m = marktext[i]; |
|
|||
1314 | if (m[0].doc.id != red.getDoc().id) continue; |
|
|||
1315 | self.chfns[self.id + '-rhs'].push(m[0].markText(m[1], m[2], m[3])); |
|
|||
1316 | } |
|
|||
1317 | }); |
|
|||
1318 |
|
||||
1319 | this.trace('change', 'LCS markup time', timer.stop()); |
|
|||
1320 |
|
||||
1321 | // merge buttons |
|
|||
1322 | var ed = {lhs:led, rhs:red}; |
|
|||
1323 | this.element.find('.merge-button').on('click', function(ev){ |
|
|||
1324 | // side of mouseenter |
|
|||
1325 | var side = 'rhs'; |
|
|||
1326 | var oside = 'lhs'; |
|
|||
1327 | var parent = jQuery(this).parents('#' + self.id + '-editor-lhs'); |
|
|||
1328 | if (parent.length) { |
|
|||
1329 | side = 'lhs'; |
|
|||
1330 | oside = 'rhs'; |
|
|||
1331 | } |
|
|||
1332 | var pos = ed[side].coordsChar({left:ev.pageX, top:ev.pageY}); |
|
|||
1333 |
|
||||
1334 | // get the change id |
|
|||
1335 | var cid = null; |
|
|||
1336 | var info = ed[side].lineInfo(pos.line); |
|
|||
1337 | jQuery.each(info.bgClass.split(' '), function(i, clazz) { |
|
|||
1338 | if (clazz.indexOf('cid-') == 0) { |
|
|||
1339 | cid = parseInt(clazz.split('-')[1], 10); |
|
|||
1340 | return false; |
|
|||
1341 | } |
|
|||
1342 | }); |
|
|||
1343 | var change = self.changes[cid]; |
|
|||
1344 | self._merge_change(change, side, oside); |
|
|||
1345 | return false; |
|
|||
1346 | }); |
|
|||
1347 |
|
||||
1348 | // gutter markup |
|
|||
1349 | var lhsLineNumbers = $('#mergely-lhs ~ .CodeMirror').find('.CodeMirror-linenumber'); |
|
|||
1350 | var rhsLineNumbers = $('#mergely-rhs ~ .CodeMirror').find('.CodeMirror-linenumber'); |
|
|||
1351 | rhsLineNumbers.removeClass('mergely current'); |
|
|||
1352 | lhsLineNumbers.removeClass('mergely current'); |
|
|||
1353 | for (var i = 0; i < changes.length; ++i) { |
|
|||
1354 | if (current_diff == i && change.op !== 'd') { |
|
|||
1355 | var change = changes[i]; |
|
|||
1356 | var j, jf = change['rhs-line-from'], jt = change['rhs-line-to'] + 1; |
|
|||
1357 | for (j = jf; j < jt; j++) { |
|
|||
1358 | var n = (j + 1).toString(); |
|
|||
1359 | rhsLineNumbers |
|
|||
1360 | .filter(function(i, node) { return $(node).text() === n; }) |
|
|||
1361 | .addClass('mergely current'); |
|
|||
1362 | } |
|
|||
1363 | } |
|
|||
1364 | if (current_diff == i && change.op !== 'a') { |
|
|||
1365 | var change = changes[i]; |
|
|||
1366 | jf = change['lhs-line-from'], jt = change['lhs-line-to'] + 1; |
|
|||
1367 | for (j = jf; j < jt; j++) { |
|
|||
1368 | var n = (j + 1).toString(); |
|
|||
1369 | lhsLineNumbers |
|
|||
1370 | .filter(function(i, node) { return $(node).text() === n; }) |
|
|||
1371 | .addClass('mergely current'); |
|
|||
1372 | } |
|
|||
1373 | } |
|
|||
1374 | } |
|
|||
1375 |
|
||||
1376 | this.trace('change', 'markup buttons time', timer.stop()); |
|
|||
1377 | }, |
|
|||
1378 | _merge_change : function(change, side, oside) { |
|
|||
1379 | if (!change) return; |
|
|||
1380 | var led = this.editor[this.id+'-lhs']; |
|
|||
1381 | var red = this.editor[this.id+'-rhs']; |
|
|||
1382 | var ed = {lhs:led, rhs:red}; |
|
|||
1383 | var i, from, to; |
|
|||
1384 |
|
||||
1385 | var text = ed[side].getRange( |
|
|||
1386 | CodeMirror.Pos(change[side + '-line-from'], 0), |
|
|||
1387 | CodeMirror.Pos(change[side + '-line-to'] + 1, 0)); |
|
|||
1388 |
|
||||
1389 | if (change['op'] == 'c') { |
|
|||
1390 | ed[oside].replaceRange(text, |
|
|||
1391 | CodeMirror.Pos(change[oside + '-line-from'], 0), |
|
|||
1392 | CodeMirror.Pos(change[oside + '-line-to'] + 1, 0)); |
|
|||
1393 | } |
|
|||
1394 | else if (side == 'rhs') { |
|
|||
1395 | if (change['op'] == 'a') { |
|
|||
1396 | ed[oside].replaceRange(text, |
|
|||
1397 | CodeMirror.Pos(change[oside + '-line-from'] + 1, 0), |
|
|||
1398 | CodeMirror.Pos(change[oside + '-line-to'] + 1, 0)); |
|
|||
1399 | } |
|
|||
1400 | else {// 'd' |
|
|||
1401 | from = parseInt(change[oside + '-line-from'], 10); |
|
|||
1402 | to = parseInt(change[oside + '-line-to'], 10); |
|
|||
1403 | for (i = to; i >= from; --i) { |
|
|||
1404 | ed[oside].setCursor({line: i, ch: -1}); |
|
|||
1405 | ed[oside].execCommand('deleteLine'); |
|
|||
1406 | } |
|
|||
1407 | } |
|
|||
1408 | } |
|
|||
1409 | else if (side == 'lhs') { |
|
|||
1410 | if (change['op'] == 'a') { |
|
|||
1411 | from = parseInt(change[oside + '-line-from'], 10); |
|
|||
1412 | to = parseInt(change[oside + '-line-to'], 10); |
|
|||
1413 | for (i = to; i >= from; --i) { |
|
|||
1414 | //ed[oside].removeLine(i); |
|
|||
1415 | ed[oside].setCursor({line: i, ch: -1}); |
|
|||
1416 | ed[oside].execCommand('deleteLine'); |
|
|||
1417 | } |
|
|||
1418 | } |
|
|||
1419 | else {// 'd' |
|
|||
1420 | ed[oside].replaceRange( text, |
|
|||
1421 | CodeMirror.Pos(change[oside + '-line-from'] + 1, 0)); |
|
|||
1422 | } |
|
|||
1423 | } |
|
|||
1424 | //reset |
|
|||
1425 | ed['lhs'].setValue(ed['lhs'].getValue()); |
|
|||
1426 | ed['rhs'].setValue(ed['rhs'].getValue()); |
|
|||
1427 |
|
||||
1428 | this._scroll_to_change(change); |
|
|||
1429 | }, |
|
|||
1430 | _draw_info: function(editor_name1, editor_name2) { |
|
|||
1431 | var visible_page_height = jQuery(this.editor[editor_name1].getScrollerElement()).height(); |
|
|||
1432 | var gutter_height = jQuery(this.editor[editor_name1].getScrollerElement()).children(':first-child').height(); |
|
|||
1433 | var dcanvas = document.getElementById(editor_name1 + '-' + editor_name2 + '-canvas'); |
|
|||
1434 | if (dcanvas == undefined) throw 'Failed to find: ' + editor_name1 + '-' + editor_name2 + '-canvas'; |
|
|||
1435 | var clhs = this.element.find('#' + this.id + '-lhs-margin'); |
|
|||
1436 | var crhs = this.element.find('#' + this.id + '-rhs-margin'); |
|
|||
1437 | return { |
|
|||
1438 | visible_page_height: visible_page_height, |
|
|||
1439 | gutter_height: gutter_height, |
|
|||
1440 | visible_page_ratio: (visible_page_height / gutter_height), |
|
|||
1441 | margin_ratio: (visible_page_height / gutter_height), |
|
|||
1442 | lhs_scroller: jQuery(this.editor[editor_name1].getScrollerElement()), |
|
|||
1443 | rhs_scroller: jQuery(this.editor[editor_name2].getScrollerElement()), |
|
|||
1444 | lhs_lines: this.editor[editor_name1].lineCount(), |
|
|||
1445 | rhs_lines: this.editor[editor_name2].lineCount(), |
|
|||
1446 | dcanvas: dcanvas, |
|
|||
1447 | clhs: clhs, |
|
|||
1448 | crhs: crhs, |
|
|||
1449 | lhs_xyoffset: jQuery(clhs).offset(), |
|
|||
1450 | rhs_xyoffset: jQuery(crhs).offset() |
|
|||
1451 | }; |
|
|||
1452 | }, |
|
|||
1453 | _draw_diff: function(editor_name1, editor_name2, changes) { |
|
|||
1454 | var ex = this._draw_info(editor_name1, editor_name2); |
|
|||
1455 | var mcanvas_lhs = ex.clhs.get(0); |
|
|||
1456 | var mcanvas_rhs = ex.crhs.get(0); |
|
|||
1457 | var ctx = ex.dcanvas.getContext('2d'); |
|
|||
1458 | var ctx_lhs = mcanvas_lhs.getContext('2d'); |
|
|||
1459 | var ctx_rhs = mcanvas_rhs.getContext('2d'); |
|
|||
1460 |
|
||||
1461 | this.trace('draw', 'visible_page_height', ex.visible_page_height); |
|
|||
1462 | this.trace('draw', 'gutter_height', ex.gutter_height); |
|
|||
1463 | this.trace('draw', 'visible_page_ratio', ex.visible_page_ratio); |
|
|||
1464 | this.trace('draw', 'lhs-scroller-top', ex.lhs_scroller.scrollTop()); |
|
|||
1465 | this.trace('draw', 'rhs-scroller-top', ex.rhs_scroller.scrollTop()); |
|
|||
1466 |
|
||||
1467 | jQuery.each(this.element.find('canvas'), function () { |
|
|||
1468 | jQuery(this).get(0).height = ex.visible_page_height; |
|
|||
1469 | }); |
|
|||
1470 |
|
||||
1471 | ex.clhs.unbind('click'); |
|
|||
1472 | ex.crhs.unbind('click'); |
|
|||
1473 |
|
||||
1474 | ctx_lhs.beginPath(); |
|
|||
1475 | ctx_lhs.fillStyle = this.settings.bgcolor; |
|
|||
1476 | ctx_lhs.strokeStyle = '#888'; |
|
|||
1477 | ctx_lhs.fillRect(0, 0, 6.5, ex.visible_page_height); |
|
|||
1478 | ctx_lhs.strokeRect(0, 0, 6.5, ex.visible_page_height); |
|
|||
1479 |
|
||||
1480 | ctx_rhs.beginPath(); |
|
|||
1481 | ctx_rhs.fillStyle = this.settings.bgcolor; |
|
|||
1482 | ctx_rhs.strokeStyle = '#888'; |
|
|||
1483 | ctx_rhs.fillRect(0, 0, 6.5, ex.visible_page_height); |
|
|||
1484 | ctx_rhs.strokeRect(0, 0, 6.5, ex.visible_page_height); |
|
|||
1485 |
|
||||
1486 | var vp = this._get_viewport(editor_name1, editor_name2); |
|
|||
1487 | for (var i = 0; i < changes.length; ++i) { |
|
|||
1488 | var change = changes[i]; |
|
|||
1489 | var fill = this.settings.fgcolor[change['op']]; |
|
|||
1490 | if (this._current_diff==i) { |
|
|||
1491 | fill = '#000'; |
|
|||
1492 | } |
|
|||
1493 |
|
||||
1494 | this.trace('draw', change); |
|
|||
1495 | // margin indicators |
|
|||
1496 | var lhs_y_start = ((change['lhs-y-start'] + ex.lhs_scroller.scrollTop()) * ex.visible_page_ratio); |
|
|||
1497 | var lhs_y_end = ((change['lhs-y-end'] + ex.lhs_scroller.scrollTop()) * ex.visible_page_ratio) + 1; |
|
|||
1498 | var rhs_y_start = ((change['rhs-y-start'] + ex.rhs_scroller.scrollTop()) * ex.visible_page_ratio); |
|
|||
1499 | var rhs_y_end = ((change['rhs-y-end'] + ex.rhs_scroller.scrollTop()) * ex.visible_page_ratio) + 1; |
|
|||
1500 | this.trace('draw', 'marker calculated', lhs_y_start, lhs_y_end, rhs_y_start, rhs_y_end); |
|
|||
1501 |
|
||||
1502 | ctx_lhs.beginPath(); |
|
|||
1503 | ctx_lhs.fillStyle = fill; |
|
|||
1504 | ctx_lhs.strokeStyle = '#000'; |
|
|||
1505 | ctx_lhs.lineWidth = 0.5; |
|
|||
1506 | ctx_lhs.fillRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5)); |
|
|||
1507 | ctx_lhs.strokeRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5)); |
|
|||
1508 |
|
||||
1509 | ctx_rhs.beginPath(); |
|
|||
1510 | ctx_rhs.fillStyle = fill; |
|
|||
1511 | ctx_rhs.strokeStyle = '#000'; |
|
|||
1512 | ctx_rhs.lineWidth = 0.5; |
|
|||
1513 | ctx_rhs.fillRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5)); |
|
|||
1514 | ctx_rhs.strokeRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5)); |
|
|||
1515 |
|
||||
1516 | if (!this._is_change_in_view(vp, change)) { |
|
|||
1517 | continue; |
|
|||
1518 | } |
|
|||
1519 |
|
||||
1520 | lhs_y_start = change['lhs-y-start']; |
|
|||
1521 | lhs_y_end = change['lhs-y-end']; |
|
|||
1522 | rhs_y_start = change['rhs-y-start']; |
|
|||
1523 | rhs_y_end = change['rhs-y-end']; |
|
|||
1524 |
|
||||
1525 | var radius = 3; |
|
|||
1526 |
|
||||
1527 | // draw left box |
|
|||
1528 | ctx.beginPath(); |
|
|||
1529 | ctx.strokeStyle = fill; |
|
|||
1530 | ctx.lineWidth = (this._current_diff==i) ? 1.5 : 1; |
|
|||
1531 |
|
||||
1532 | var rectWidth = this.draw_lhs_width; |
|
|||
1533 | var rectHeight = lhs_y_end - lhs_y_start - 1; |
|
|||
1534 | var rectX = this.draw_lhs_min; |
|
|||
1535 | var rectY = lhs_y_start; |
|
|||
1536 | // top and top top-right corner |
|
|||
1537 |
|
||||
1538 | // draw left box |
|
|||
1539 | ctx.moveTo(rectX, rectY); |
|
|||
1540 | if (navigator.appName == 'Microsoft Internet Explorer') { |
|
|||
1541 | // IE arcs look awful |
|
|||
1542 | ctx.lineTo(this.draw_lhs_min + this.draw_lhs_width, lhs_y_start); |
|
|||
1543 | ctx.lineTo(this.draw_lhs_min + this.draw_lhs_width, lhs_y_end + 1); |
|
|||
1544 | ctx.lineTo(this.draw_lhs_min, lhs_y_end + 1); |
|
|||
1545 | } |
|
|||
1546 | else { |
|
|||
1547 | if (rectHeight <= 0) { |
|
|||
1548 | ctx.lineTo(rectX + rectWidth, rectY); |
|
|||
1549 | } |
|
|||
1550 | else { |
|
|||
1551 | ctx.arcTo(rectX + rectWidth, rectY, rectX + rectWidth, rectY + radius, radius); |
|
|||
1552 | ctx.arcTo(rectX + rectWidth, rectY + rectHeight, rectX + rectWidth - radius, rectY + rectHeight, radius); |
|
|||
1553 | } |
|
|||
1554 | // bottom line |
|
|||
1555 | ctx.lineTo(rectX, rectY + rectHeight); |
|
|||
1556 | } |
|
|||
1557 | ctx.stroke(); |
|
|||
1558 |
|
||||
1559 | rectWidth = this.draw_rhs_width; |
|
|||
1560 | rectHeight = rhs_y_end - rhs_y_start - 1; |
|
|||
1561 | rectX = this.draw_rhs_max; |
|
|||
1562 | rectY = rhs_y_start; |
|
|||
1563 |
|
||||
1564 | // draw right box |
|
|||
1565 | ctx.moveTo(rectX, rectY); |
|
|||
1566 | if (navigator.appName == 'Microsoft Internet Explorer') { |
|
|||
1567 | ctx.lineTo(this.draw_rhs_max - this.draw_rhs_width, rhs_y_start); |
|
|||
1568 | ctx.lineTo(this.draw_rhs_max - this.draw_rhs_width, rhs_y_end + 1); |
|
|||
1569 | ctx.lineTo(this.draw_rhs_max, rhs_y_end + 1); |
|
|||
1570 | } |
|
|||
1571 | else { |
|
|||
1572 | if (rectHeight <= 0) { |
|
|||
1573 | ctx.lineTo(rectX - rectWidth, rectY); |
|
|||
1574 | } |
|
|||
1575 | else { |
|
|||
1576 | ctx.arcTo(rectX - rectWidth, rectY, rectX - rectWidth, rectY + radius, radius); |
|
|||
1577 | ctx.arcTo(rectX - rectWidth, rectY + rectHeight, rectX - radius, rectY + rectHeight, radius); |
|
|||
1578 | } |
|
|||
1579 | ctx.lineTo(rectX, rectY + rectHeight); |
|
|||
1580 | } |
|
|||
1581 | ctx.stroke(); |
|
|||
1582 |
|
||||
1583 | // connect boxes |
|
|||
1584 | var cx = this.draw_lhs_min + this.draw_lhs_width; |
|
|||
1585 | var cy = lhs_y_start + (lhs_y_end + 1 - lhs_y_start) / 2.0; |
|
|||
1586 | var dx = this.draw_rhs_max - this.draw_rhs_width; |
|
|||
1587 | var dy = rhs_y_start + (rhs_y_end + 1 - rhs_y_start) / 2.0; |
|
|||
1588 | ctx.moveTo(cx, cy); |
|
|||
1589 | if (cy == dy) { |
|
|||
1590 | ctx.lineTo(dx, dy); |
|
|||
1591 | } |
|
|||
1592 | else { |
|
|||
1593 | // fancy! |
|
|||
1594 | ctx.bezierCurveTo( |
|
|||
1595 | cx + 12, cy - 3, // control-1 X,Y |
|
|||
1596 | dx - 12, dy - 3, // control-2 X,Y |
|
|||
1597 | dx, dy); |
|
|||
1598 | } |
|
|||
1599 | ctx.stroke(); |
|
|||
1600 | } |
|
|||
1601 |
|
||||
1602 | // visible window feedback |
|
|||
1603 | ctx_lhs.fillStyle = this.settings.vpcolor; |
|
|||
1604 | ctx_rhs.fillStyle = this.settings.vpcolor; |
|
|||
1605 |
|
||||
1606 | var lto = ex.clhs.height() * ex.visible_page_ratio; |
|
|||
1607 | var lfrom = (ex.lhs_scroller.scrollTop() / ex.gutter_height) * ex.clhs.height(); |
|
|||
1608 | var rto = ex.crhs.height() * ex.visible_page_ratio; |
|
|||
1609 | var rfrom = (ex.rhs_scroller.scrollTop() / ex.gutter_height) * ex.crhs.height(); |
|
|||
1610 | this.trace('draw', 'cls.height', ex.clhs.height()); |
|
|||
1611 | this.trace('draw', 'lhs_scroller.scrollTop()', ex.lhs_scroller.scrollTop()); |
|
|||
1612 | this.trace('draw', 'gutter_height', ex.gutter_height); |
|
|||
1613 | this.trace('draw', 'visible_page_ratio', ex.visible_page_ratio); |
|
|||
1614 | this.trace('draw', 'lhs from', lfrom, 'lhs to', lto); |
|
|||
1615 | this.trace('draw', 'rhs from', rfrom, 'rhs to', rto); |
|
|||
1616 |
|
||||
1617 | ctx_lhs.fillRect(1.5, lfrom, 4.5, lto); |
|
|||
1618 | ctx_rhs.fillRect(1.5, rfrom, 4.5, rto); |
|
|||
1619 |
|
||||
1620 | ex.clhs.click(function (ev) { |
|
|||
1621 | var y = ev.pageY - ex.lhs_xyoffset.top - (lto / 2); |
|
|||
1622 | var sto = Math.max(0, (y / mcanvas_lhs.height) * ex.lhs_scroller.get(0).scrollHeight); |
|
|||
1623 | ex.lhs_scroller.scrollTop(sto); |
|
|||
1624 | }); |
|
|||
1625 | ex.crhs.click(function (ev) { |
|
|||
1626 | var y = ev.pageY - ex.rhs_xyoffset.top - (rto / 2); |
|
|||
1627 | var sto = Math.max(0, (y / mcanvas_rhs.height) * ex.rhs_scroller.get(0).scrollHeight); |
|
|||
1628 | ex.rhs_scroller.scrollTop(sto); |
|
|||
1629 | }); |
|
|||
1630 | }, |
|
|||
1631 | trace: function(name) { |
|
|||
1632 | if(this.settings._debug.indexOf(name) >= 0) { |
|
|||
1633 | arguments[0] = name + ':'; |
|
|||
1634 | console.log([].slice.apply(arguments)); |
|
|||
1635 | } |
|
|||
1636 | } |
|
|||
1637 | }); |
|
|||
1638 |
|
||||
1639 | jQuery.pluginMaker = function(plugin) { |
|
|||
1640 | // add the plugin function as a jQuery plugin |
|
|||
1641 | jQuery.fn[plugin.prototype.name] = function(options) { |
|
|||
1642 | // get the arguments |
|
|||
1643 | var args = jQuery.makeArray(arguments), |
|
|||
1644 | after = args.slice(1); |
|
|||
1645 | var rc; |
|
|||
1646 | this.each(function() { |
|
|||
1647 | // see if we have an instance |
|
|||
1648 | var instance = jQuery.data(this, plugin.prototype.name); |
|
|||
1649 | if (instance) { |
|
|||
1650 | // call a method on the instance |
|
|||
1651 | if (typeof options == "string") { |
|
|||
1652 | rc = instance[options].apply(instance, after); |
|
|||
1653 | } else if (instance.update) { |
|
|||
1654 | // call update on the instance |
|
|||
1655 | return instance.update.apply(instance, args); |
|
|||
1656 | } |
|
|||
1657 | } else { |
|
|||
1658 | // create the plugin |
|
|||
1659 | var _plugin = new plugin(this, options); |
|
|||
1660 | } |
|
|||
1661 | }); |
|
|||
1662 | if (rc != undefined) return rc; |
|
|||
1663 | }; |
|
|||
1664 | }; |
|
|||
1665 |
|
||||
1666 | // make the mergely widget |
|
|||
1667 | jQuery.pluginMaker(Mgly.mergely); |
|
|||
1668 |
|
||||
1669 | })( window, document, jQuery, CodeMirror ); No newline at end of file |
|
@@ -1,225 +0,0 b'' | |||||
1 | ## -*- coding: utf-8 -*- |
|
|||
2 |
|
||||
3 | <%inherit file="/base/base.html"/> |
|
|||
4 | <%namespace name="diff_block" file="/changeset/diff_block.html"/> |
|
|||
5 |
|
||||
6 | <%def name="js_extra()"> |
|
|||
7 | <script type="text/javascript" src="${h.asset('js/mergerly.js')}"></script> |
|
|||
8 | </%def> |
|
|||
9 |
|
||||
10 | <%def name="css_extra()"> |
|
|||
11 | <link rel="stylesheet" type="text/css" href="${h.asset('css/mergerly.css')}"/> |
|
|||
12 | </%def> |
|
|||
13 |
|
||||
14 | <%def name="title()"> |
|
|||
15 | ${_('%s File side-by-side diff') % c.repo_name} |
|
|||
16 | %if c.rhodecode_name: |
|
|||
17 | · ${h.branding(c.rhodecode_name)} |
|
|||
18 | %endif |
|
|||
19 | </%def> |
|
|||
20 |
|
||||
21 | <%def name="breadcrumbs_links()"> |
|
|||
22 | r${c.commit_1.revision}:${h.short_id(c.commit_1.raw_id)} ... r${c.commit_2.revision}:${h.short_id(c.commit_2.raw_id)} |
|
|||
23 | </%def> |
|
|||
24 |
|
||||
25 | <%def name="menu_bar_nav()"> |
|
|||
26 | ${self.menu_items(active='repositories')} |
|
|||
27 | </%def> |
|
|||
28 |
|
||||
29 | <%def name="menu_bar_subnav()"> |
|
|||
30 | ${self.repo_menu(active='changelog')} |
|
|||
31 | </%def> |
|
|||
32 |
|
||||
33 | <%def name="main()"> |
|
|||
34 | <div class="box"> |
|
|||
35 | <div class="title"> |
|
|||
36 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
|||
37 | </div> |
|
|||
38 |
|
||||
39 | <div class="breadcrumbs"> |
|
|||
40 | ${_('Side-by-side Diff')} r${c.commit_1.revision}:${h.short_id(c.commit_1.raw_id)} ... r${c.commit_2.revision}:${h.short_id(c.commit_2.raw_id)} |
|
|||
41 | </div> |
|
|||
42 | <div class="cs_files"> |
|
|||
43 | <table class="compare_view_files commit_diff"> |
|
|||
44 | <tr class="cs_${c.diff_data['operation']} collapse_file" fid="${c.FID}"> |
|
|||
45 | <td class="cs_icon_td"> |
|
|||
46 | <span class="collapse_file_icon" fid="${c.FID}"></span> |
|
|||
47 | </td> |
|
|||
48 | <td class="cs_icon_td"> |
|
|||
49 | <div class="flag_status not_reviewed hidden"></div> |
|
|||
50 | </td> |
|
|||
51 | <td class="cs_${c.diff_data['operation']}" id="a_${c.FID}"> |
|
|||
52 | <div class="node"> |
|
|||
53 | <a href="#a_${c.FID}"> |
|
|||
54 | <i class="icon-file-${c.diff_data['operation'].lower()}"></i> |
|
|||
55 | ${h.safe_unicode(c.node1.path)} |
|
|||
56 | </a> |
|
|||
57 | </div> |
|
|||
58 | </td> |
|
|||
59 | <td> |
|
|||
60 | <div class="changes pull-right">${h.fancy_file_stats(c.diff_data['stats'])}</div> |
|
|||
61 | <div class="comment-bubble pull-right" data-path="${h.safe_unicode(c.node1.path)}"> |
|
|||
62 | <i class="icon-comment"></i> |
|
|||
63 | </div> |
|
|||
64 | </td> |
|
|||
65 | </tr> |
|
|||
66 | <tr fid="${c.FID}" id="diff_${c.FID}" class="diff_links"> |
|
|||
67 | <td></td> |
|
|||
68 | <td></td> |
|
|||
69 | <td class="cs_${c.diff_data['operation']}"> |
|
|||
70 | ${diff_block.diff_menu(c.repo_name, h.safe_unicode(c.node1.path), c.commit_1.raw_id, c.commit_2.raw_id, c.diff_data['operation'])} |
|
|||
71 | </td> |
|
|||
72 | <td class="td-actions rc-form"> |
|
|||
73 | <div id="ignorews" class="btn-link show-inline-comments"> |
|
|||
74 | <span data-enabled=false class="toggle">${_('Ignore whitespace')}</span> |
|
|||
75 | <span data-enabled=true class="toggle" style="display: none">${_('Show whitespace')}</span> |
|
|||
76 | </div> | |
|
|||
77 |
|
||||
78 | <div id="edit_mode" class="btn-link show-inline-comments"> |
|
|||
79 | <span data-enabled=true class="toggle">${_('Enable editor mode')}</span> |
|
|||
80 | <span data-enabled=false class="toggle" style="display: none">${_('Disable editor mode')}</span> |
|
|||
81 | </div> | |
|
|||
82 |
|
||||
83 | <div class="btn-link show-inline-comments"> |
|
|||
84 | <span id="prev_change" title="${_('Previous change')}"><i class="icon-left"></i></span> |
|
|||
85 | <span id="next_change" title="${_('Next change')}"><i class="icon-right"></i></span> |
|
|||
86 | </div> |
|
|||
87 |
|
||||
88 | </td> |
|
|||
89 | </tr> |
|
|||
90 | <tr id="tr_${c.FID}"> |
|
|||
91 | <td></td> |
|
|||
92 | <td></td> |
|
|||
93 | <td class="injected_diff" colspan="2"> |
|
|||
94 | <div class="diff-container" id="${'diff-container-%s' % (id(c.diff_data['operation']))}"> |
|
|||
95 | <div id="${c.FID}" class="diffblock margined comm"> |
|
|||
96 | <div class="diff-container" > |
|
|||
97 | <div class="diffblock comm sidebyside"> |
|
|||
98 | <div class="code-header"> |
|
|||
99 | <div class="changeset_header"> |
|
|||
100 | ${_('mode')}: <span id="selected_mode">plain</span> | |
|
|||
101 | </div> |
|
|||
102 | </div> |
|
|||
103 | <div id="compare"></div> |
|
|||
104 | </div> |
|
|||
105 | </div> |
|
|||
106 | </div> |
|
|||
107 | </div> |
|
|||
108 | </td> |
|
|||
109 | </tr> |
|
|||
110 | </table> |
|
|||
111 | </div> |
|
|||
112 |
|
||||
113 |
|
||||
114 | <script> |
|
|||
115 | var orig1_url = '${h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),revision=c.commit_1.raw_id)}'; |
|
|||
116 | var orig2_url = '${h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node2.path),revision=c.commit_2.raw_id)}'; |
|
|||
117 | $(document).ready(function () { |
|
|||
118 |
|
||||
119 |
|
||||
120 | var editor = $('#compare'); |
|
|||
121 | editor.mergely({ |
|
|||
122 | autoupdate: true, |
|
|||
123 | width: 'auto', |
|
|||
124 | height: '600', |
|
|||
125 | fgcolor: {a: '#ddffdd', c: '#cccccc', d: '#ffdddd'}, |
|
|||
126 | bgcolor: '#fff', |
|
|||
127 | viewport: false, |
|
|||
128 | cmsettings: { |
|
|||
129 | mode: 'text/plain', |
|
|||
130 | readOnly: true, |
|
|||
131 | lineWrapping: false, |
|
|||
132 | lineNumbers: true |
|
|||
133 | } |
|
|||
134 | }); |
|
|||
135 |
|
||||
136 |
|
||||
137 | var lhs = function(deferred) { |
|
|||
138 | if ("${c.node1.is_binary}" == "True") { |
|
|||
139 | deferred.resolve('Binary file'); |
|
|||
140 | } |
|
|||
141 | else if ("${c.node1.commit.__class__.__name__}" == "EmptyCommit") { |
|
|||
142 | deferred.resolve(''); |
|
|||
143 | } |
|
|||
144 | else { |
|
|||
145 | editor.mergely('lhs', 'loading diff...'); |
|
|||
146 | $.ajax(orig1_url, { |
|
|||
147 | dataType: 'text', |
|
|||
148 | success: function(data) { |
|
|||
149 | // call the complete function to let CodeMirror know |
|
|||
150 | deferred.resolve(data); |
|
|||
151 | } |
|
|||
152 | }); |
|
|||
153 | } |
|
|||
154 | }; |
|
|||
155 |
|
||||
156 | var rhs = function(deferred) { |
|
|||
157 | if ("${c.node2.is_binary}" == "True") { |
|
|||
158 | deferred.resolve('Binary file'); |
|
|||
159 | } |
|
|||
160 | else if ("${c.node2.commit.__class__.__name__}" == "EmptyCommit") { |
|
|||
161 | deferred.resolve(''); |
|
|||
162 | } |
|
|||
163 | else { |
|
|||
164 | editor.mergely('rhs', 'loading diff...'); |
|
|||
165 | $.ajax(orig2_url, { |
|
|||
166 | dataType: 'text', |
|
|||
167 | success: function(data) { |
|
|||
168 | // call the complete function to let CodeMirror know |
|
|||
169 | deferred.resolve(data); |
|
|||
170 | } |
|
|||
171 | }); |
|
|||
172 | } |
|
|||
173 | }; |
|
|||
174 |
|
||||
175 | var deferred_lhs = $.Deferred(); |
|
|||
176 | var deferred_rhs = $.Deferred(); |
|
|||
177 | $.when( |
|
|||
178 | deferred_lhs, deferred_rhs |
|
|||
179 | ).done(function(lhs_response, rhs_response) { |
|
|||
180 | editor.mergely('lhs', lhs_response); |
|
|||
181 | editor.mergely('rhs', rhs_response); |
|
|||
182 |
|
||||
183 | var detected_mode = detectCodeMirrorModeFromExt( |
|
|||
184 | '${h.safe_unicode(c.node1.path.split("/")[-1])}', true); |
|
|||
185 | if (detected_mode) { |
|
|||
186 | setCodeMirrorMode(editor.mergely('cm', 'lhs'), detected_mode); |
|
|||
187 | setCodeMirrorMode(editor.mergely('cm', 'rhs'), detected_mode); |
|
|||
188 | $('#selected_mode').html(detected_mode); |
|
|||
189 | } |
|
|||
190 | }); |
|
|||
191 | // load via ajax, and use deferred signals to notify when finished. |
|
|||
192 | lhs(deferred_lhs); |
|
|||
193 | rhs(deferred_rhs); |
|
|||
194 |
|
||||
195 | $("#ignorews").click(function() { |
|
|||
196 | $("#ignorews .toggle").toggle(); |
|
|||
197 | var val = $('#ignorews .toggle:visible').data()['enabled']; |
|
|||
198 | editor.mergely('options', {ignorews: val}); |
|
|||
199 | editor.mergely('update'); |
|
|||
200 | }); |
|
|||
201 |
|
||||
202 | $("#edit_mode").click(function() { |
|
|||
203 | $("#edit_mode .toggle").toggle(); |
|
|||
204 | var val = $('#edit_mode .toggle:visible').data()['enabled']; |
|
|||
205 | editor.mergely('cm', 'lhs').setOption('readOnly', val); |
|
|||
206 | editor.mergely('cm', 'rhs').setOption('readOnly', val); |
|
|||
207 | editor.mergely('update'); |
|
|||
208 | }); |
|
|||
209 |
|
||||
210 | $('#prev_change').on('click', function() { |
|
|||
211 | editor.mergely('scrollToDiff', 'prev'); |
|
|||
212 | }); |
|
|||
213 | $('#next_change').on('click', function() { |
|
|||
214 | editor.mergely('scrollToDiff', 'next'); |
|
|||
215 | }); |
|
|||
216 |
|
||||
217 | // extend content dynamically on this component for readability |
|
|||
218 | $('#content').css({'max-width': '2000px'}); |
|
|||
219 | editor.mergely('resize'); |
|
|||
220 |
|
||||
221 | }); |
|
|||
222 | </script> |
|
|||
223 |
|
||||
224 | </div> |
|
|||
225 | </%def> |
|
@@ -1,159 +0,0 b'' | |||||
1 | <%inherit file="/base/base.html"/> |
|
|||
2 | <%namespace name="diff_block" file="/changeset/diff_block.html"/> |
|
|||
3 | <%def name="title()"> |
|
|||
4 | ${_('%s File Diff') % c.repo_name} |
|
|||
5 | %if c.rhodecode_name: |
|
|||
6 | · ${h.branding(c.rhodecode_name)} |
|
|||
7 | %endif |
|
|||
8 | </%def> |
|
|||
9 |
|
||||
10 | <%def name="breadcrumbs_links()"> |
|
|||
11 | ${_('Compare')} |
|
|||
12 | r${c.commit_1.revision}:${h.short_id(c.commit_1.raw_id)} |
|
|||
13 | % if c.filename1 != c.filename: |
|
|||
14 | <i class="icon-file"></i> ${c.filename1} |
|
|||
15 | % endif |
|
|||
16 | ... |
|
|||
17 | r${c.commit_2.revision}:${h.short_id(c.commit_2.raw_id)} |
|
|||
18 | </%def> |
|
|||
19 |
|
||||
20 | <%def name="menu_bar_nav()"> |
|
|||
21 | ${self.menu_items(active='repositories')} |
|
|||
22 | </%def> |
|
|||
23 |
|
||||
24 | <%def name="menu_bar_subnav()"> |
|
|||
25 | ${self.repo_menu(active='changelog')} |
|
|||
26 | </%def> |
|
|||
27 |
|
||||
28 | <%def name="breadcrumbs_links()"> |
|
|||
29 | ${_('Compare')} |
|
|||
30 | r${c.commit_1.revision}:${h.short_id(c.commit_1.raw_id)} |
|
|||
31 | % if c.filename1 != c.filename: |
|
|||
32 | <i class="icon-file"></i> ${c.filename1} |
|
|||
33 | % endif |
|
|||
34 | ... |
|
|||
35 | r${c.commit_2.revision}:${h.short_id(c.commit_2.raw_id)} |
|
|||
36 | % if c.filename1 == c.filename: |
|
|||
37 | ${_('for')} <i class="icon-file"></i> ${c.filename1} |
|
|||
38 | % endif |
|
|||
39 | </%def> |
|
|||
40 |
|
||||
41 | <%def name="main()"> |
|
|||
42 | <div class="box"> |
|
|||
43 |
|
||||
44 | <div class="title"> |
|
|||
45 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
|||
46 | </div> |
|
|||
47 |
|
||||
48 | ${self.breadcrumbs()} |
|
|||
49 |
|
||||
50 | <div class="compare-header"> |
|
|||
51 |
|
||||
52 | %if not c.commit_ranges: |
|
|||
53 | <p class="empty_data">${_('No commits')}</p> |
|
|||
54 |
|
||||
55 | %else: |
|
|||
56 | <div class="compare-label">${_('Target')}</div> |
|
|||
57 | <div class="compare-value"> |
|
|||
58 | <code> |
|
|||
59 | ${h.link_to('r%s:%s' % (c.commit_1.revision, h.short_id(c.commit_1.raw_id)), h.url('changeset_home',repo_name=c.repo_name, revision=c.commit_1.raw_id))} |
|
|||
60 | </code> |
|
|||
61 | </div> |
|
|||
62 | <div class="compare-label">${_('Source')}</div> |
|
|||
63 | <div class="compare-value"> |
|
|||
64 | <code> |
|
|||
65 | ${h.link_to('r%s:%s' % (c.commit_2.revision, h.short_id(c.commit_2.raw_id)), h.url('changeset_home',repo_name=c.repo_name, revision=c.commit_2.raw_id))} |
|
|||
66 | </code> |
|
|||
67 | </div> |
|
|||
68 | %endif |
|
|||
69 | </div> |
|
|||
70 |
|
||||
71 | ##CS |
|
|||
72 | <%include file="../compare/compare_commits.html" /> |
|
|||
73 |
|
||||
74 | ## FILES |
|
|||
75 | <div class="cs_files_title"> |
|
|||
76 | <span class="cs_files_expand"> |
|
|||
77 | <span id="expand_all_files">${_('Expand All')}</span> | <span id="collapse_all_files">${_('Collapse All')}</span> |
|
|||
78 | </span> |
|
|||
79 | <h2> |
|
|||
80 | % if c.binary_file: |
|
|||
81 | ${_('Cannot diff binary files')} |
|
|||
82 | % elif (c.lines_added == 0 and c.lines_deleted == 0): |
|
|||
83 | ${_('File was not changed in this commit range')} |
|
|||
84 | % else: |
|
|||
85 | ${diff_block.diff_summary_text(len(c.files), c.lines_added, c.lines_deleted)} |
|
|||
86 | % endif |
|
|||
87 | </h2> |
|
|||
88 | </div> |
|
|||
89 |
|
||||
90 | % if (c.lines_added > 0 or c.lines_deleted > 0): |
|
|||
91 | <div class="cs_files"> |
|
|||
92 | <table class="compare_view_files commit_diff"> |
|
|||
93 | %for FID, (cs1, cs2, change, path, diff, stats, file) in c.changes.iteritems(): |
|
|||
94 | <tr class="cs_${change} collapse_file" fid="${FID}"> |
|
|||
95 | <td class="cs_icon_td"> |
|
|||
96 | <span class="collapse_file_icon" fid="${FID}"></span> |
|
|||
97 | </td> |
|
|||
98 | <td class="cs_icon_td"> |
|
|||
99 | <div class="flag_status not_reviewed hidden"></div> |
|
|||
100 | </td> |
|
|||
101 | <td class="cs_${change}" id="a_${FID}"> |
|
|||
102 | <div class="node"> |
|
|||
103 | <a href="#a_${FID}"> |
|
|||
104 | <i class="icon-file-${change.lower()}"></i> |
|
|||
105 | ${h.safe_unicode(path)} |
|
|||
106 | </a> |
|
|||
107 | </div> |
|
|||
108 | </td> |
|
|||
109 | <td> |
|
|||
110 | %if (stats): |
|
|||
111 | <div class="changes pull-right">${h.fancy_file_stats(stats)}</div> |
|
|||
112 | %endif |
|
|||
113 | <div class="comment-bubble pull-right" data-path="${path}"> |
|
|||
114 | <i class="icon-comment"></i> |
|
|||
115 | </div> |
|
|||
116 | </td> |
|
|||
117 | </tr> |
|
|||
118 | <tr fid="${FID}" id="diff_${FID}" class="diff_links"> |
|
|||
119 | <td></td> |
|
|||
120 | <td></td> |
|
|||
121 | <td class="cs_${change}"> |
|
|||
122 | ${diff_block.diff_menu(c.repo_name, h.safe_unicode(path), cs1, cs2, change, file)} |
|
|||
123 | </td> |
|
|||
124 | <td class="td-actions rc-form"> |
|
|||
125 | ${c.ignorews_url(request.GET, h.FID(cs2,path))} | |
|
|||
126 | ${c.context_url(request.GET, h.FID(cs2,path))} | |
|
|||
127 | <div data-comment-id="${h.FID(cs2,path)}" class="btn-link show-inline-comments comments-visible"> |
|
|||
128 | <span class="comments-show">${_('Show comments')}</span> |
|
|||
129 | <span class="comments-hide">${_('Hide comments')}</span> |
|
|||
130 | </div> |
|
|||
131 | </td> |
|
|||
132 | </tr> |
|
|||
133 | <tr id="tr_${FID}"> |
|
|||
134 | <td></td> |
|
|||
135 | <td></td> |
|
|||
136 | <td class="injected_diff" colspan="2"> |
|
|||
137 | <div class="diff-container" id="${'diff-container-%s' % (id(change))}"> |
|
|||
138 | <div id="${FID}" class="diffblock margined comm"> |
|
|||
139 | <div class="code-body"> |
|
|||
140 | <div class="full_f_path" path="${h.safe_unicode(path)}"></div> |
|
|||
141 | ${diff|n} |
|
|||
142 | % if file and file["is_limited_diff"]: |
|
|||
143 | % if file["exceeds_limit"]: |
|
|||
144 | ${diff_block.file_message()} |
|
|||
145 | % else: |
|
|||
146 | <h5>${_('Diff was truncated. File content available only in full diff.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5> |
|
|||
147 | % endif |
|
|||
148 | % endif |
|
|||
149 | </div> |
|
|||
150 | </div> |
|
|||
151 | </div> |
|
|||
152 | </td> |
|
|||
153 | </tr> |
|
|||
154 | %endfor |
|
|||
155 | </table> |
|
|||
156 | </div> |
|
|||
157 | % endif |
|
|||
158 | </div> |
|
|||
159 | </%def> |
|
General Comments 0
You need to be logged in to leave comments.
Login now