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 | 1066 | '/{repo_name}/annotate/{revision}/{f_path}', |
|
1067 | 1067 | controller='files', action='index', revision='tip', |
|
1068 | 1068 | f_path='', annotate=True, conditions={'function': check_repo}, |
|
1069 | requirements=URL_NAME_REQUIREMENTS) | |
|
1069 | requirements=URL_NAME_REQUIREMENTS, jsroute=True) | |
|
1070 | 1070 | |
|
1071 | 1071 | rmap.connect('files_edit', |
|
1072 | 1072 | '/{repo_name}/edit/{revision}/{f_path}', |
@@ -90,6 +90,7 b' class CompareController(BaseRepoControll' | |||
|
90 | 90 | c.target_ref_type = "" |
|
91 | 91 | c.commit_statuses = ChangesetStatus.STATUSES |
|
92 | 92 | c.preview_mode = False |
|
93 | c.file_path = None | |
|
93 | 94 | return render('compare/compare_diff.html') |
|
94 | 95 | |
|
95 | 96 | @LoginRequired() |
@@ -103,8 +104,10 b' class CompareController(BaseRepoControll' | |||
|
103 | 104 | |
|
104 | 105 | # target_ref will be evaluated in target_repo |
|
105 | 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 | 111 | c.commit_statuses = ChangesetStatus.STATUSES |
|
109 | 112 | |
|
110 | 113 | # if merge is True |
@@ -115,7 +118,6 b' class CompareController(BaseRepoControll' | |||
|
115 | 118 | # if merge is False |
|
116 | 119 | # Show a raw diff of source/target refs even if no ancestor exists |
|
117 | 120 | |
|
118 | ||
|
119 | 121 | # c.fulldiff disables cut_off_limit |
|
120 | 122 | c.fulldiff = str2bool(request.GET.get('fulldiff')) |
|
121 | 123 | |
@@ -131,7 +133,8 b' class CompareController(BaseRepoControll' | |||
|
131 | 133 | target_repo=source_repo_name, |
|
132 | 134 | target_ref_type=source_ref_type, |
|
133 | 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 | 139 | source_repo = Repository.get_by_repo_name(source_repo_name) |
|
137 | 140 | target_repo = Repository.get_by_repo_name(target_repo_name) |
@@ -151,8 +154,11 b' class CompareController(BaseRepoControll' | |||
|
151 | 154 | h.flash(msg, category='error') |
|
152 | 155 | return redirect(url('compare_home', repo_name=c.repo_name)) |
|
153 | 156 | |
|
154 |
source_ |
|
|
155 |
target_ |
|
|
157 | source_scm = source_repo.scm_instance() | |
|
158 | target_scm = target_repo.scm_instance() | |
|
159 | ||
|
160 | source_alias = source_scm.alias | |
|
161 | target_alias = target_scm.alias | |
|
156 | 162 | if source_alias != target_alias: |
|
157 | 163 | msg = _('The comparison of two different kinds of remote repos ' |
|
158 | 164 | 'is not available') |
@@ -175,9 +181,6 b' class CompareController(BaseRepoControll' | |||
|
175 | 181 | c.source_ref_type = source_ref_type |
|
176 | 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 | 184 | pre_load = ["author", "branch", "date", "message"] |
|
182 | 185 | c.ancestor = None |
|
183 | 186 | try: |
@@ -199,9 +202,9 b' class CompareController(BaseRepoControll' | |||
|
199 | 202 | c.statuses = c.rhodecode_db_repo.statuses( |
|
200 | 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 | 206 | if not c.ancestor: |
|
204 | return '' # cannot merge if there is no ancestor | |
|
207 | return '' # cannot merge if there is no ancestor | |
|
205 | 208 | return render('compare/compare_commits.html') |
|
206 | 209 | |
|
207 | 210 | if c.ancestor: |
@@ -238,7 +241,8 b' class CompareController(BaseRepoControll' | |||
|
238 | 241 | |
|
239 | 242 | txtdiff = source_repo.scm_instance().get_diff( |
|
240 | 243 | commit1=source_commit, commit2=target_commit, |
|
241 |
path1=source_path |
|
|
244 | path=target_path, path1=source_path) | |
|
245 | ||
|
242 | 246 | diff_processor = diffs.DiffProcessor( |
|
243 | 247 | txtdiff, format='newdiff', diff_limit=diff_limit, |
|
244 | 248 | file_limit=file_limit, show_full_diff=c.fulldiff) |
@@ -260,5 +264,7 b' class CompareController(BaseRepoControll' | |||
|
260 | 264 | ).render_patchset(_parsed, source_ref, target_ref) |
|
261 | 265 | |
|
262 | 266 | c.preview_mode = merge |
|
267 | c.source_commit = source_commit | |
|
268 | c.target_commit = target_commit | |
|
263 | 269 | |
|
264 | 270 | return render('compare/compare_diff.html') |
@@ -799,21 +799,15 b' class FilesController(BaseRepoController' | |||
|
799 | 799 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
800 | 800 | 'repository.admin') |
|
801 | 801 | def diff(self, repo_name, f_path): |
|
802 | ignore_whitespace = request.GET.get('ignorews') == '1' | |
|
803 |
|
|
|
802 | ||
|
803 | c.action = request.GET.get('diff') | |
|
804 | 804 | diff1 = request.GET.get('diff1', '') |
|
805 | diff2 = request.GET.get('diff2', '') | |
|
805 | 806 | |
|
806 | 807 | path1, diff1 = parse_path_ref(diff1, default_path=f_path) |
|
807 | 808 | |
|
808 | diff2 = request.GET.get('diff2', '') | |
|
809 |
|
|
|
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] = [] | |
|
809 | ignore_whitespace = str2bool(request.GET.get('ignorews')) | |
|
810 | line_context = request.GET.get('context', 3) | |
|
817 | 811 | |
|
818 | 812 | if not any((diff1, diff2)): |
|
819 | 813 | h.flash( |
@@ -821,18 +815,16 b' class FilesController(BaseRepoController' | |||
|
821 | 815 | category='error') |
|
822 | 816 | raise HTTPBadRequest() |
|
823 | 817 | |
|
824 | # special case if we want a show commit_id only, it's impl here | |
|
825 | # to reduce JS and callbacks | |
|
826 | ||
|
827 | if request.GET.get('show_rev') and diff1: | |
|
828 | if str2bool(request.GET.get('annotate', 'False')): | |
|
829 | _url = url('files_annotate_home', repo_name=c.repo_name, | |
|
830 | revision=diff1, f_path=path1) | |
|
831 | else: | |
|
832 | _url = url('files_home', repo_name=c.repo_name, | |
|
833 |
|
|
|
834 | ||
|
835 | return redirect(_url) | |
|
818 | if c.action not in ['download', 'raw']: | |
|
819 | # redirect to new view if we render diff | |
|
820 | return redirect( | |
|
821 | url('compare_url', repo_name=repo_name, | |
|
822 | source_ref_type='rev', | |
|
823 | source_ref=diff1, | |
|
824 | target_repo=c.repo_name, | |
|
825 | target_ref_type='rev', | |
|
826 | target_ref=diff2, | |
|
827 | f_path=f_path)) | |
|
836 | 828 | |
|
837 | 829 | try: |
|
838 | 830 | node1 = self._get_file_node(diff1, path1) |
@@ -877,98 +869,40 b' class FilesController(BaseRepoController' | |||
|
877 | 869 | return diff.as_raw() |
|
878 | 870 | |
|
879 | 871 | else: |
|
880 | fid = h.FID(diff2, node2.path) | |
|
881 | line_context_lcl = get_line_ctx(fid, request.GET) | |
|
882 | ign_whitespace_lcl = get_ignore_ws(fid, request.GET) | |
|
883 | ||
|
884 | __, commit1, commit2, diff, st, data = diffs.wrapped_diff( | |
|
885 | filenode_old=node1, | |
|
886 | filenode_new=node2, | |
|
887 | diff_limit=self.cut_off_limit_diff, | |
|
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') | |
|
872 | return redirect( | |
|
873 | url('compare_url', repo_name=repo_name, | |
|
874 | source_ref_type='rev', | |
|
875 | source_ref=diff1, | |
|
876 | target_repo=c.repo_name, | |
|
877 | target_ref_type='rev', | |
|
878 | target_ref=diff2, | |
|
879 | f_path=f_path)) | |
|
917 | 880 | |
|
918 | 881 | @LoginRequired() |
|
919 | 882 | @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', |
|
920 | 883 | 'repository.admin') |
|
921 | 884 | def diff_2way(self, repo_name, f_path): |
|
885 | """ | |
|
886 | Kept only to make OLD links work | |
|
887 | """ | |
|
922 | 888 | diff1 = request.GET.get('diff1', '') |
|
923 | 889 | diff2 = request.GET.get('diff2', '') |
|
924 | 890 | |
|
925 | nodes = [] | |
|
926 | unknown_commits = [] | |
|
927 | for commit in [diff1, diff2]: | |
|
928 |
|
|
|
929 | nodes.append(self._get_file_node(commit, f_path)) | |
|
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 | |
|
891 | if not any((diff1, diff2)): | |
|
892 | h.flash( | |
|
893 | 'Need query parameter "diff1" or "diff2" to generate a diff.', | |
|
894 | category='error') | |
|
895 | raise HTTPBadRequest() | |
|
945 | 896 | |
|
946 | f_gitdiff = diffs.get_gitdiff(node1, node2, ignore_whitespace=False) | |
|
947 | diff_processor = diffs.DiffProcessor(f_gitdiff, format='gitdiff') | |
|
948 | diff_data = diff_processor.prepare() | |
|
949 | ||
|
950 | if not diff_data or diff_data[0]['raw_diff'] == '': | |
|
951 | h.flash(h.literal(_('%(file_path)s has not changed ' | |
|
952 | 'between %(commit_1)s and %(commit_2)s.') % { | |
|
953 |
|
|
|
954 | 'commit_1': node1.commit.id, | |
|
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') | |
|
897 | return redirect( | |
|
898 | url('compare_url', repo_name=repo_name, | |
|
899 | source_ref_type='rev', | |
|
900 | source_ref=diff1, | |
|
901 | target_repo=c.repo_name, | |
|
902 | target_ref_type='rev', | |
|
903 | target_ref=diff2, | |
|
904 | f_path=f_path, | |
|
905 | diffmode='sideside')) | |
|
972 | 906 | |
|
973 | 907 | def _get_file_node(self, commit_id, f_path): |
|
974 | 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 | 27 | from rhodecode.lib import helpers as h |
|
28 | 28 | from rhodecode.lib.vcs.exceptions import RepositoryError |
|
29 | 29 | |
|
30 | ||
|
30 | 31 | def parse_path_ref(ref, default_path=None): |
|
31 | 32 | """ |
|
32 | 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 | 79 | commit_id = ref_name |
|
79 | if repo_scm.alias != 'svn': # pass svn refs straight to backend until | |
|
80 | # the branch issue with svn is fixed | |
|
80 | if repo_scm.alias != 'svn': # pass svn refs straight to backend until | |
|
81 | # the branch issue with svn is fixed | |
|
81 | 82 | if ref_type and ref_type in ref_type_mapping: |
|
82 | 83 | try: |
|
83 | 84 | commit_id = ref_type_mapping[ref_type][ref_name] |
@@ -378,6 +378,7 b' class BaseRepository(object):' | |||
|
378 | 378 | parameter works only for backends which support diff generation for |
|
379 | 379 | different paths. Other backends will raise a `ValueError` if `path1` |
|
380 | 380 | is set and has a different value than `path`. |
|
381 | :param file_path: filter this diff by given path pattern | |
|
381 | 382 | """ |
|
382 | 383 | raise NotImplementedError |
|
383 | 384 | |
@@ -1540,9 +1541,10 b' class Diff(object):' | |||
|
1540 | 1541 | """ |
|
1541 | 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 | 1548 | _header_re = None |
|
1547 | 1549 | |
|
1548 | 1550 | def __init__(self, raw_diff): |
@@ -1554,10 +1556,19 b' class Diff(object):' | |||
|
1554 | 1556 | to make diffs consistent we must prepend with \n, and make sure |
|
1555 | 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 | 1567 | total_chunks = len(chunks) |
|
1559 | return (DiffChunk(chunk, self, cur_chunk == total_chunks) | |
|
1560 | for cur_chunk, chunk in enumerate(chunks, start=1)) | |
|
1568 | ||
|
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 | 1574 | class DiffChunk(object): |
@@ -30,6 +30,10 b' from rhodecode.lib.vcs.backends import b' | |||
|
30 | 30 | |
|
31 | 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 | 37 | _header_re = re.compile(r""" |
|
34 | 38 | #^diff[ ]--git |
|
35 | 39 | [ ]"?a/(?P<a_path>.+?)"?[ ]"?b/(?P<b_path>.+?)"?\n |
@@ -1477,8 +1477,9 b' table.integrations {' | |||
|
1477 | 1477 | margin-left: 8px; |
|
1478 | 1478 | } |
|
1479 | 1479 | |
|
1480 |
|
|
|
1480 | div.ancestor { | |
|
1481 | 1481 | margin: @padding 0; |
|
1482 | line-height: 3.0em; | |
|
1482 | 1483 | } |
|
1483 | 1484 | |
|
1484 | 1485 | .cs_icon_td input[type="checkbox"] { |
@@ -72,6 +72,7 b'' | |||
|
72 | 72 | } |
|
73 | 73 | .disabled { |
|
74 | 74 | opacity: .5; |
|
75 | cursor: inherit; | |
|
75 | 76 | } |
|
76 | 77 | .help-block { |
|
77 | 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 | 87 | * Splits remainder |
|
78 | 88 | * |
@@ -112,7 +112,7 b'' | |||
|
112 | 112 | |
|
113 | 113 | <div class="fieldset"> |
|
114 | 114 | <div class="left-label"> |
|
115 | ${_('Diffs')}: | |
|
115 | ${_('Diff options')}: | |
|
116 | 116 | </div> |
|
117 | 117 | <div class="right-content"> |
|
118 | 118 | <div class="diff-actions"> |
@@ -29,29 +29,84 b'' | |||
|
29 | 29 | </%def> |
|
30 | 30 | |
|
31 | 31 | <%def name="main()"> |
|
32 | <div class="summary-header"> | |
|
32 | <div class="summary-header"> | |
|
33 | 33 | <div class="title"> |
|
34 | <div class="title-content"> | |
|
35 | 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 | 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"> | |
|
47 | <div class="title"> | |
|
48 | <h2> | |
|
49 | ${self.breadcrumbs_links()} | |
|
50 | </h2> | |
|
65 | <%doc> | |
|
66 | ##TODO(marcink): implement this and diff menus | |
|
67 | <div class="fieldset"> | |
|
68 | <div class="left-label"> | |
|
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 | 108 | </div> |
|
52 | </div> | |
|
53 | <div id="changeset_compare_view_content"> | |
|
54 | ##CS | |
|
109 | ## Commit range generated below | |
|
55 | 110 | <%include file="../compare/compare_commits.html"/> |
|
56 | 111 | <div class="cs_files"> |
|
57 | 112 | <%namespace name="cbdiffs" file="/codeblocks/diffs.html"/> |
@@ -65,7 +120,6 b'' | |||
|
65 | 120 | commit=commit, |
|
66 | 121 | )} |
|
67 | 122 | %endfor |
|
68 | </table> | |
|
69 | 123 | </div> |
|
70 | 124 | </div> |
|
71 | 125 | </%def> |
@@ -52,45 +52,6 b'' | |||
|
52 | 52 | </div> |
|
53 | 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 | 56 | <%def name="diff_summary_text(changed_files, lines_added, lines_deleted, limited_diff=False)"> |
|
96 | 57 | % if limited_diff: |
@@ -162,10 +162,11 b' collapse_all = len(diffset.files) > coll' | |||
|
162 | 162 | |
|
163 | 163 | <div class="filediffs"> |
|
164 | 164 | %for i, filediff in enumerate(diffset.files): |
|
165 | <% | |
|
166 | lines_changed = filediff['patch']['stats']['added'] + filediff['patch']['stats']['deleted'] | |
|
167 | over_lines_changed_limit = lines_changed > lines_changed_limit | |
|
168 | %> | |
|
165 | ||
|
166 | <% | |
|
167 | lines_changed = filediff['patch']['stats']['added'] + filediff['patch']['stats']['deleted'] | |
|
168 | over_lines_changed_limit = lines_changed > lines_changed_limit | |
|
169 | %> | |
|
169 | 170 | <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox"> |
|
170 | 171 | <div |
|
171 | 172 | class="filediff" |
@@ -414,6 +415,7 b' from rhodecode.lib.diffs import NEW_FILE' | |||
|
414 | 415 | if line.modified.lineno: |
|
415 | 416 | new_line_anchor = diff_line_anchor(hunk.filediff.target_file_path, line.modified.lineno, 'n') |
|
416 | 417 | %> |
|
418 | ||
|
417 | 419 | <tr class="cb-line"> |
|
418 | 420 | <td class="cb-data ${action_class(line.original.action)}" |
|
419 | 421 | data-line-number="${line.original.lineno}" |
@@ -544,6 +546,7 b' from rhodecode.lib.diffs import NEW_FILE' | |||
|
544 | 546 | <div class="diffset-menu clearinner"> |
|
545 | 547 | <div class="pull-right"> |
|
546 | 548 | <div class="btn-group"> |
|
549 | ||
|
547 | 550 | <a |
|
548 | 551 | class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip" |
|
549 | 552 | title="${_('View side by side')}" |
@@ -557,20 +560,21 b' from rhodecode.lib.diffs import NEW_FILE' | |||
|
557 | 560 | </a> |
|
558 | 561 | </div> |
|
559 | 562 | </div> |
|
563 | ||
|
560 | 564 | <div class="pull-left"> |
|
561 | 565 | <div class="btn-group"> |
|
562 | 566 | <a |
|
563 | 567 | class="btn" |
|
564 | 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 | 570 | <a |
|
567 | 571 | class="btn" |
|
568 | 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 | 574 | <a |
|
571 | 575 | class="btn" |
|
572 | 576 | href="#" |
|
573 | onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode')}</a> | |
|
577 | onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode Diff')}</a> | |
|
574 | 578 | </div> |
|
575 | 579 | </div> |
|
576 | 580 | </div> |
@@ -1,20 +1,17 b'' | |||
|
1 | 1 | ## Changesets table ! |
|
2 | 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 |
|
|
|
9 |
|
|
|
10 |
|
|
|
11 |
|
|
|
12 |
|
|
|
13 |
|
|
|
14 |
|
|
|
15 | </p> | |
|
16 |
|
|
|
4 | %if c.ancestor: | |
|
5 | <div class="ancestor">${_('Common Ancestor Commit')}: | |
|
6 | <a href="${h.url('changeset_home', | |
|
7 | repo_name=c.repo_name, | |
|
8 | revision=c.ancestor)}"> | |
|
9 | ${h.short_id(c.ancestor)} | |
|
10 | </a> | |
|
11 | </div> | |
|
12 | %endif | |
|
17 | 13 | |
|
14 | <div class="container"> | |
|
18 | 15 | <input type="hidden" name="__start__" value="revisions:sequence"> |
|
19 | 16 | <table class="rctable compare_view_commits"> |
|
20 | 17 | <tr> |
@@ -66,9 +63,21 b'' | |||
|
66 | 63 | </td> |
|
67 | 64 | </tr> |
|
68 | 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 | 78 | </table> |
|
70 | 79 | <input type="hidden" name="__end__" value="revisions:sequence"> |
|
71 | %endif | |
|
80 | ||
|
72 | 81 | </div> |
|
73 | 82 | |
|
74 | 83 | <script> |
@@ -76,7 +85,7 b'' | |||
|
76 | 85 | var target_expand = $(this); |
|
77 | 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 | 89 | if (target_expand.hasClass('open')){ |
|
81 | 90 | $('#c-'+cid).css({ |
|
82 | 91 | 'height': '1.5em', |
@@ -34,34 +34,132 b'' | |||
|
34 | 34 | <div class="box"> |
|
35 | 35 | <div class="title"> |
|
36 | 36 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
37 | <div class="breadcrumbs"> | |
|
38 | ${_('Compare Commits')} | |
|
39 | </div> | |
|
40 | 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 | 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 | 163 | ## use JS script to load it quickly before potentially large diffs render long time |
|
66 | 164 | ## this prevents from situation when large diffs block rendering of select2 fields |
|
67 | 165 | <script type="text/javascript"> |
@@ -241,13 +339,26 b'' | |||
|
241 | 339 | |
|
242 | 340 | </div> |
|
243 | 341 | |
|
244 | %if c.compare_home: | |
|
342 | %if not c.compare_home: | |
|
245 | 343 | <div id="changeset_compare_view_content"> |
|
246 | <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div> | |
|
247 |
< |
|
|
248 | %else: | |
|
249 | <div id="changeset_compare_view_content"> | |
|
250 |
|
|
|
344 | <div class="pull-left"> | |
|
345 | <div class="btn-group"> | |
|
346 | <a | |
|
347 | class="btn" | |
|
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 | 362 | <%include file="compare_commits.html"/> |
|
252 | 363 | ${cbdiffs.render_diffset_menu()} |
|
253 | 364 | ${cbdiffs.render_diffset(c.diffset)} |
@@ -9,11 +9,9 b'' | |||
|
9 | 9 | </%def> |
|
10 | 10 | |
|
11 | 11 | <%def name="js_extra()"> |
|
12 | <script type="text/javascript" src="${h.asset('js/mergerly.js', ver=c.rhodecode_version_hash)}"></script> | |
|
13 | 12 | </%def> |
|
14 | 13 | |
|
15 | 14 | <%def name="css_extra()"> |
|
16 | <link rel="stylesheet" type="text/css" href="${h.asset('css/mergerly.css', ver=c.rhodecode_version_hash)}"/> | |
|
17 | 15 | </%def> |
|
18 | 16 | |
|
19 | 17 |
@@ -128,7 +128,7 b'' | |||
|
128 | 128 | // used for history, and switch to |
|
129 | 129 | var initialCommitData = { |
|
130 | 130 | id: null, |
|
131 |
text: '${_(" |
|
|
131 | text: '${_("Pick Commit")}', | |
|
132 | 132 | type: 'sha', |
|
133 | 133 | raw_id: null, |
|
134 | 134 | files_url: null |
@@ -151,9 +151,47 b'' | |||
|
151 | 151 | |
|
152 | 152 | // file history select2 |
|
153 | 153 | select2FileHistorySwitcher('#diff1', initialCommitData, state); |
|
154 | ||
|
155 | // show at, diff to actions handlers | |
|
154 | 156 | $('#diff1').on('change', function(e) { |
|
155 | $('#diff').removeClass('disabled').removeAttr("disabled"); | |
|
156 | $('#show_rev').removeClass('disabled').removeAttr("disabled"); | |
|
157 | $('#diff_to_commit').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 | 197 | // show more authors |
@@ -46,17 +46,29 b'' | |||
|
46 | 46 | </div> |
|
47 | 47 | |
|
48 | 48 | |
|
49 |
<div |
|
|
50 | ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')} | |
|
49 | <div class="fieldset collapsable-content" data-toggle="summary-details"> | |
|
50 | <div class="left-label"> | |
|
51 | ${_('Show/Diff file')}: | |
|
52 | </div> | |
|
53 | <div class="right-content"> | |
|
51 | 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")} | |
|
55 | ${h.submit('show_rev',_('Show at Commit'),class_="btn disabled",disabled="true")} | |
|
56 | ${h.hidden('annotate', c.annotate)} | |
|
57 | ${h.end_form()} | |
|
60 | ||
|
61 | <div class="fieldset collapsable-content" data-toggle="summary-details"> | |
|
62 | <div class="left-label"> | |
|
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 | 69 | </div> |
|
59 | 70 | |
|
71 | ||
|
60 | 72 | <script> |
|
61 | 73 | collapsableContent(); |
|
62 | 74 | </script> No newline at end of file |
@@ -360,14 +360,33 b'' | |||
|
360 | 360 | </div> |
|
361 | 361 | % endif |
|
362 | 362 | <div class="compare_view_commits_title"> |
|
363 | % if c.allowed_to_update and not c.pull_request.is_closed(): | |
|
364 | <a id="update_commits" class="btn btn-primary pull-right">${_('Update commits')}</a> | |
|
365 |
|
|
|
366 | <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a> | |
|
367 |
|
|
|
368 | % if len(c.commit_ranges): | |
|
369 | <h2>${ungettext('Compare View: %s commit','Compare View: %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}</h2> | |
|
370 | % endif | |
|
363 | ||
|
364 | <div class="pull-left"> | |
|
365 | <div class="btn-group"> | |
|
366 | <a | |
|
367 | class="btn" | |
|
368 | href="#" | |
|
369 | onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false"> | |
|
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 | 390 | </div> |
|
372 | 391 | % if not c.missing_commits: |
|
373 | 392 | <%include file="/compare/compare_commits.html" /> |
@@ -244,7 +244,8 b' class TestCommitCommentsController(TestC' | |||
|
244 | 244 | ('markdown', '# header', '<h1>header</h1>'), |
|
245 | 245 | ('markdown', '*italics*', '<em>italics</em>'), |
|
246 | 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 | 249 | def test_preview(self, renderer, input, output, backend): |
|
249 | 250 | self.log_user() |
|
250 | 251 | params = { |
@@ -22,16 +22,13 b' import mock' | |||
|
22 | 22 | import pytest |
|
23 | 23 | import lxml.html |
|
24 | 24 | |
|
25 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |
|
26 | 25 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError |
|
27 | from rhodecode.model.db import Repository | |
|
28 | from rhodecode.model.scm import ScmModel | |
|
29 | from rhodecode.tests import url, TEST_USER_ADMIN_LOGIN, assert_session_flash | |
|
30 | from rhodecode.tests.utils import AssertResponse | |
|
26 | from rhodecode.tests import url, assert_session_flash | |
|
27 | from rhodecode.tests.utils import AssertResponse, commit_change | |
|
31 | 28 | |
|
32 | 29 | |
|
33 | 30 | @pytest.mark.usefixtures("autologin_user", "app") |
|
34 | class TestCompareController: | |
|
31 | class TestCompareController(object): | |
|
35 | 32 | |
|
36 | 33 | @pytest.mark.xfail_backends("svn", reason="Requires pull") |
|
37 | 34 | def test_compare_remote_with_different_commit_indexes(self, backend): |
@@ -53,23 +50,23 b' class TestCompareController:' | |||
|
53 | 50 | fork = backend.create_repo() |
|
54 | 51 | |
|
55 | 52 | # prepare fork |
|
56 |
commit0 = |
|
|
53 | commit0 = commit_change( | |
|
57 | 54 | fork.repo_name, filename='file1', content='A', |
|
58 | 55 | message='A', vcs_type=backend.alias, parent=None, newfile=True) |
|
59 | 56 | |
|
60 |
commit1 = |
|
|
57 | commit1 = commit_change( | |
|
61 | 58 | fork.repo_name, filename='file1', content='B', |
|
62 | 59 | message='B, child of A', vcs_type=backend.alias, parent=commit0) |
|
63 | 60 | |
|
64 |
|
|
|
61 | commit_change( # commit 2 | |
|
65 | 62 | fork.repo_name, filename='file1', content='C', |
|
66 | 63 | message='C, child of B', vcs_type=backend.alias, parent=commit1) |
|
67 | 64 | |
|
68 |
commit3 = |
|
|
65 | commit3 = commit_change( | |
|
69 | 66 | fork.repo_name, filename='file1', content='D', |
|
70 | 67 | message='D, child of A', vcs_type=backend.alias, parent=commit0) |
|
71 | 68 | |
|
72 |
commit4 = |
|
|
69 | commit4 = commit_change( | |
|
73 | 70 | fork.repo_name, filename='file1', content='E', |
|
74 | 71 | message='E, child of D', vcs_type=backend.alias, parent=commit3) |
|
75 | 72 | |
@@ -105,7 +102,7 b' class TestCompareController:' | |||
|
105 | 102 | repo1 = backend.create_repo() |
|
106 | 103 | |
|
107 | 104 | # commit something ! |
|
108 |
commit0 = |
|
|
105 | commit0 = commit_change( | |
|
109 | 106 | repo1.repo_name, filename='file1', content='line1\n', |
|
110 | 107 | message='commit1', vcs_type=backend.alias, parent=None, |
|
111 | 108 | newfile=True) |
@@ -114,11 +111,11 b' class TestCompareController:' | |||
|
114 | 111 | repo2 = backend.create_fork() |
|
115 | 112 | |
|
116 | 113 | # add two extra commit into fork |
|
117 |
commit1 = |
|
|
114 | commit1 = commit_change( | |
|
118 | 115 | repo2.repo_name, filename='file1', content='line1\nline2\n', |
|
119 | 116 | message='commit2', vcs_type=backend.alias, parent=commit0) |
|
120 | 117 | |
|
121 |
commit2 = |
|
|
118 | commit2 = commit_change( | |
|
122 | 119 | repo2.repo_name, filename='file1', content='line1\nline2\nline3\n', |
|
123 | 120 | message='commit3', vcs_type=backend.alias, parent=commit1) |
|
124 | 121 | |
@@ -156,7 +153,7 b' class TestCompareController:' | |||
|
156 | 153 | repo1 = backend.create_repo() |
|
157 | 154 | |
|
158 | 155 | # commit something ! |
|
159 |
commit0 = |
|
|
156 | commit0 = commit_change( | |
|
160 | 157 | repo1.repo_name, filename='file1', content='line1\n', |
|
161 | 158 | message='commit1', vcs_type=backend.alias, parent=None, |
|
162 | 159 | newfile=True) |
@@ -165,17 +162,17 b' class TestCompareController:' | |||
|
165 | 162 | repo2 = backend.create_fork() |
|
166 | 163 | |
|
167 | 164 | # now commit something to origin repo |
|
168 |
|
|
|
165 | commit_change( | |
|
169 | 166 | repo1.repo_name, filename='file2', content='line1file2\n', |
|
170 | 167 | message='commit2', vcs_type=backend.alias, parent=commit0, |
|
171 | 168 | newfile=True) |
|
172 | 169 | |
|
173 | 170 | # add two extra commit into fork |
|
174 |
commit1 = |
|
|
171 | commit1 = commit_change( | |
|
175 | 172 | repo2.repo_name, filename='file1', content='line1\nline2\n', |
|
176 | 173 | message='commit2', vcs_type=backend.alias, parent=commit0) |
|
177 | 174 | |
|
178 |
commit2 = |
|
|
175 | commit2 = commit_change( | |
|
179 | 176 | repo2.repo_name, filename='file1', content='line1\nline2\nline3\n', |
|
180 | 177 | message='commit3', vcs_type=backend.alias, parent=commit1) |
|
181 | 178 | |
@@ -207,9 +204,9 b' class TestCompareController:' | |||
|
207 | 204 | compare_page.swap_is_hidden() |
|
208 | 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 | 209 | def test_compare_of_unrelated_forks(self, backend): |
|
212 | # TODO: johbo: Fails for git due to some other issue it seems | |
|
213 | 210 | orig = backend.create_repo(number_of_commits=1) |
|
214 | 211 | fork = backend.create_repo(number_of_commits=1) |
|
215 | 212 | |
@@ -245,11 +242,11 b' class TestCompareController:' | |||
|
245 | 242 | repo1 = backend.create_repo() |
|
246 | 243 | |
|
247 | 244 | # commit something ! |
|
248 |
commit0 = |
|
|
245 | commit0 = commit_change( | |
|
249 | 246 | repo1.repo_name, filename='file1', content='line1\n', |
|
250 | 247 | message='commit1', vcs_type=backend.alias, parent=None, |
|
251 | 248 | newfile=True) |
|
252 |
commit1 = |
|
|
249 | commit1 = commit_change( | |
|
253 | 250 | repo1.repo_name, filename='file1', content='line1\nline2\n', |
|
254 | 251 | message='commit2', vcs_type=backend.alias, parent=commit0) |
|
255 | 252 | |
@@ -257,18 +254,18 b' class TestCompareController:' | |||
|
257 | 254 | repo2 = backend.create_fork() |
|
258 | 255 | |
|
259 | 256 | # now make commit3-6 |
|
260 |
commit2 = |
|
|
257 | commit2 = commit_change( | |
|
261 | 258 | repo1.repo_name, filename='file1', content='line1\nline2\nline3\n', |
|
262 | 259 | message='commit3', vcs_type=backend.alias, parent=commit1) |
|
263 |
commit3 = |
|
|
260 | commit3 = commit_change( | |
|
264 | 261 | repo1.repo_name, filename='file1', |
|
265 | 262 | content='line1\nline2\nline3\nline4\n', message='commit4', |
|
266 | 263 | vcs_type=backend.alias, parent=commit2) |
|
267 |
commit4 = |
|
|
264 | commit4 = commit_change( | |
|
268 | 265 | repo1.repo_name, filename='file1', |
|
269 | 266 | content='line1\nline2\nline3\nline4\nline5\n', message='commit5', |
|
270 | 267 | vcs_type=backend.alias, parent=commit3) |
|
271 |
|
|
|
268 | commit_change( # commit 5 | |
|
272 | 269 | repo1.repo_name, filename='file1', |
|
273 | 270 | content='line1\nline2\nline3\nline4\nline5\nline6\n', |
|
274 | 271 | message='commit6', vcs_type=backend.alias, parent=commit4) |
@@ -311,11 +308,11 b' class TestCompareController:' | |||
|
311 | 308 | repo1 = backend.create_repo() |
|
312 | 309 | |
|
313 | 310 | # commit something ! |
|
314 |
commit0 = |
|
|
311 | commit0 = commit_change( | |
|
315 | 312 | repo1.repo_name, filename='file1', content='line1\n', |
|
316 | 313 | message='commit1', vcs_type=backend.alias, parent=None, |
|
317 | 314 | newfile=True) |
|
318 |
commit1 = |
|
|
315 | commit1 = commit_change( | |
|
319 | 316 | repo1.repo_name, filename='file1', content='line1\nline2\n', |
|
320 | 317 | message='commit2', vcs_type=backend.alias, parent=commit0) |
|
321 | 318 | |
@@ -323,18 +320,18 b' class TestCompareController:' | |||
|
323 | 320 | backend.create_fork() |
|
324 | 321 | |
|
325 | 322 | # now make commit3-6 |
|
326 |
commit2 = |
|
|
323 | commit2 = commit_change( | |
|
327 | 324 | repo1.repo_name, filename='file1', content='line1\nline2\nline3\n', |
|
328 | 325 | message='commit3', vcs_type=backend.alias, parent=commit1) |
|
329 |
commit3 = |
|
|
326 | commit3 = commit_change( | |
|
330 | 327 | repo1.repo_name, filename='file1', |
|
331 | 328 | content='line1\nline2\nline3\nline4\n', message='commit4', |
|
332 | 329 | vcs_type=backend.alias, parent=commit2) |
|
333 |
commit4 = |
|
|
330 | commit4 = commit_change( | |
|
334 | 331 | repo1.repo_name, filename='file1', |
|
335 | 332 | content='line1\nline2\nline3\nline4\nline5\n', message='commit5', |
|
336 | 333 | vcs_type=backend.alias, parent=commit3) |
|
337 |
commit5 = |
|
|
334 | commit5 = commit_change( | |
|
338 | 335 | repo1.repo_name, filename='file1', |
|
339 | 336 | content='line1\nline2\nline3\nline4\nline5\nline6\n', |
|
340 | 337 | message='commit6', vcs_type=backend.alias, parent=commit4) |
@@ -400,7 +397,7 b' class TestCompareController:' | |||
|
400 | 397 | repo1 = backend.create_repo() |
|
401 | 398 | r1_name = repo1.repo_name |
|
402 | 399 | |
|
403 |
commit0 = |
|
|
400 | commit0 = commit_change( | |
|
404 | 401 | repo=r1_name, filename='file1', |
|
405 | 402 | content='line1', message='commit1', vcs_type=backend.alias, |
|
406 | 403 | newfile=True) |
@@ -413,19 +410,19 b' class TestCompareController:' | |||
|
413 | 410 | self.r2_id = repo2.repo_id |
|
414 | 411 | r2_name = repo2.repo_name |
|
415 | 412 | |
|
416 |
commit1 = |
|
|
413 | commit1 = commit_change( | |
|
417 | 414 | repo=r2_name, filename='file1-fork', |
|
418 | 415 | content='file1-line1-from-fork', message='commit1-fork', |
|
419 | 416 | vcs_type=backend.alias, parent=repo2.scm_instance()[-1], |
|
420 | 417 | newfile=True) |
|
421 | 418 | |
|
422 |
commit2 = |
|
|
419 | commit2 = commit_change( | |
|
423 | 420 | repo=r2_name, filename='file2-fork', |
|
424 | 421 | content='file2-line1-from-fork', message='commit2-fork', |
|
425 | 422 | vcs_type=backend.alias, parent=commit1, |
|
426 | 423 | newfile=True) |
|
427 | 424 | |
|
428 |
|
|
|
425 | commit_change( # commit 3 | |
|
429 | 426 | repo=r2_name, filename='file3-fork', |
|
430 | 427 | content='file3-line1-from-fork', message='commit3-fork', |
|
431 | 428 | vcs_type=backend.alias, parent=commit2, newfile=True) |
@@ -447,9 +444,9 b' class TestCompareController:' | |||
|
447 | 444 | response.mustcontain('%s@%s' % (r2_name, commit_id1)) |
|
448 | 445 | response.mustcontain('%s@%s' % (r1_name, commit_id2)) |
|
449 | 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 | 450 | repo=r1_name, filename='file2', |
|
454 | 451 | content='line1-added-after-fork', message='commit2-parent', |
|
455 | 452 | vcs_type=backend.alias, parent=None, newfile=True) |
@@ -558,7 +555,7 b' class TestCompareController:' | |||
|
558 | 555 | |
|
559 | 556 | |
|
560 | 557 | @pytest.mark.usefixtures("autologin_user") |
|
561 | class TestCompareControllerSvn: | |
|
558 | class TestCompareControllerSvn(object): | |
|
562 | 559 | |
|
563 | 560 | def test_supports_references_with_path(self, app, backend_svn): |
|
564 | 561 | repo = backend_svn['svn-simple-layout'] |
@@ -574,7 +571,7 b' class TestCompareControllerSvn:' | |||
|
574 | 571 | status=200) |
|
575 | 572 | |
|
576 | 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 | 576 | # Should find only one file changed when comparing those two tags |
|
580 | 577 | response.mustcontain('example.py') |
@@ -596,7 +593,7 b' class TestCompareControllerSvn:' | |||
|
596 | 593 | status=200) |
|
597 | 594 | |
|
598 | 595 | # It should show commits |
|
599 |
assert 'No |
|
|
596 | assert 'No commits in this compare' not in response.body | |
|
600 | 597 | |
|
601 | 598 | # Should find only one file changed when comparing those two tags |
|
602 | 599 | response.mustcontain('example.py') |
@@ -660,36 +657,3 b' class ComparePage(AssertResponse):' | |||
|
660 | 657 | def target_source_are_enabled(self): |
|
661 | 658 | response = self.response |
|
662 | 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 | 44 | response.mustcontain('%s@%s' % (backend.repo_name, tag1)) |
|
45 | 45 | response.mustcontain('%s@%s' % (backend.repo_name, tag2)) |
|
46 | 46 | |
|
47 |
# outgoing c |
|
|
47 | # outgoing commits between tags | |
|
48 | 48 | commit_indexes = { |
|
49 | 49 | 'git': [113] + range(115, 121), |
|
50 | 50 | 'hg': [112] + range(115, 121), |
@@ -118,8 +118,8 b' class TestCompareController:' | |||
|
118 | 118 | response.mustcontain('%s@%s' % (backend.repo_name, head_id)) |
|
119 | 119 | |
|
120 | 120 | # branches are equal |
|
121 |
response.mustcontain(' |
|
|
122 |
response.mustcontain(' |
|
|
121 | response.mustcontain('No files') | |
|
122 | response.mustcontain('No commits in this compare') | |
|
123 | 123 | |
|
124 | 124 | def test_compare_commits(self, backend): |
|
125 | 125 | repo = backend.repo |
@@ -28,15 +28,11 b' from rhodecode.lib import helpers as h' | |||
|
28 | 28 | from rhodecode.lib.compat import OrderedDict |
|
29 | 29 | from rhodecode.lib.ext_json import json |
|
30 | 30 | from rhodecode.lib.vcs import nodes |
|
31 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |
|
31 | ||
|
32 | 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 | 33 | from rhodecode.tests import ( |
|
37 |
url |
|
|
34 | url, assert_session_flash, assert_not_in_session_flash) | |
|
38 | 35 | from rhodecode.tests.fixture import Fixture |
|
39 | from rhodecode.tests.utils import AssertResponse | |
|
40 | 36 | |
|
41 | 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 | 47 | @pytest.mark.usefixtures("app") |
|
86 | 48 | class TestFilesController: |
|
87 | 49 | |
@@ -120,7 +82,7 b' class TestFilesController:' | |||
|
120 | 82 | response = self.app.get(url( |
|
121 | 83 | controller='files', action='index', |
|
122 | 84 | repo_name=repo.repo_name, revision='tip', f_path='/')) |
|
123 |
assert_response = |
|
|
85 | assert_response = response.assert_response() | |
|
124 | 86 | assert_response.contains_one_link( |
|
125 | 87 | 'absolute-path @ 000000000000', 'http://example.com/absolute-path') |
|
126 | 88 | |
@@ -130,7 +92,7 b' class TestFilesController:' | |||
|
130 | 92 | response = self.app.get(url( |
|
131 | 93 | controller='files', action='index', |
|
132 | 94 | repo_name=repo.repo_name, revision='tip', f_path='/')) |
|
133 |
assert_response = |
|
|
95 | assert_response = response.assert_response() | |
|
134 | 96 | assert_response.contains_one_link( |
|
135 | 97 | 'subpaths-path @ 000000000000', |
|
136 | 98 | 'http://sub-base.example.com/subpaths-path') |
@@ -179,21 +141,24 b' class TestFilesController:' | |||
|
179 | 141 | assert_dirs_in_response(response, dirs, params) |
|
180 | 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 | 144 | def test_index_different_branch(self, backend): |
|
185 | # TODO: Git test repository does not contain branches | |
|
186 | # TODO: Branch support in Subversion | |
|
187 | ||
|
188 | commit = backend.repo.get_commit(commit_idx=150) | |
|
145 | branches = dict( | |
|
146 | hg=(150, ['git']), | |
|
147 | # TODO: Git test repository does not contain other branches | |
|
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 | 154 | response = self.app.get(url( |
|
190 | 155 | controller='files', action='index', |
|
191 | 156 | repo_name=backend.repo_name, |
|
192 | 157 | revision=commit.raw_id, |
|
193 | 158 | f_path='/')) |
|
194 |
assert_response = |
|
|
195 | assert_response.element_contains( | |
|
196 |
'.tags .branchtag', |
|
|
159 | assert_response = response.assert_response() | |
|
160 | for branch in branches: | |
|
161 | assert_response.element_contains('.tags .branchtag', branch) | |
|
197 | 162 | |
|
198 | 163 | def test_index_paging(self, backend): |
|
199 | 164 | repo = backend.repo |
@@ -221,7 +186,7 b' class TestFilesController:' | |||
|
221 | 186 | msgbox = """<div class="commit right-content">%s</div>""" |
|
222 | 187 | response.mustcontain(msgbox % (commit.message, )) |
|
223 | 188 | |
|
224 |
assert_response = |
|
|
189 | assert_response = response.assert_response() | |
|
225 | 190 | if commit.branch: |
|
226 | 191 | assert_response.element_contains('.tags.tags-main .branchtag', commit.branch) |
|
227 | 192 | if commit.tags: |
@@ -348,7 +313,7 b' class TestFilesController:' | |||
|
348 | 313 | f_path='/', commit_id=commit.raw_id), |
|
349 | 314 | extra_environ=xhr_header) |
|
350 | 315 | |
|
351 |
assert_response = |
|
|
316 | assert_response = response.assert_response() | |
|
352 | 317 | |
|
353 | 318 | for attr in ['data-commit-id', 'data-date', 'data-author']: |
|
354 | 319 | elements = assert_response.get_elements('[{}]'.format(attr)) |
@@ -401,7 +366,7 b' class TestFilesController:' | |||
|
401 | 366 | # TODO: johbo: Think about a better place for these tests. Either controller |
|
402 | 367 | # specific unit tests or we move down the whole logic further towards the vcs |
|
403 | 368 | # layer |
|
404 | class TestAdjustFilePathForSvn: | |
|
369 | class TestAdjustFilePathForSvn(object): | |
|
405 | 370 | """SVN specific adjustments of node history in FileController.""" |
|
406 | 371 | |
|
407 | 372 | def test_returns_path_relative_to_matched_reference(self): |
@@ -433,7 +398,7 b' class TestAdjustFilePathForSvn:' | |||
|
433 | 398 | |
|
434 | 399 | |
|
435 | 400 | @pytest.mark.usefixtures("app") |
|
436 | class TestRepositoryArchival: | |
|
401 | class TestRepositoryArchival(object): | |
|
437 | 402 | |
|
438 | 403 | def test_archival(self, backend): |
|
439 | 404 | backend.enable_downloads() |
@@ -485,7 +450,7 b' class TestRepositoryArchival:' | |||
|
485 | 450 | |
|
486 | 451 | |
|
487 | 452 | @pytest.mark.usefixtures("app", "autologin_user") |
|
488 | class TestRawFileHandling: | |
|
453 | class TestRawFileHandling(object): | |
|
489 | 454 | |
|
490 | 455 | def test_raw_file_ok(self, backend): |
|
491 | 456 | commit = backend.repo.get_commit(commit_idx=173) |
@@ -575,6 +540,7 b' class TestFilesDiff:' | |||
|
575 | 540 | def test_file_full_diff(self, backend, diff): |
|
576 | 541 | commit1 = backend.repo.get_commit(commit_idx=-1) |
|
577 | 542 | commit2 = backend.repo.get_commit(commit_idx=-2) |
|
543 | ||
|
578 | 544 | response = self.app.get( |
|
579 | 545 | url( |
|
580 | 546 | controller='files', |
@@ -582,11 +548,17 b' class TestFilesDiff:' | |||
|
582 | 548 | repo_name=backend.repo_name, |
|
583 | 549 | f_path='README'), |
|
584 | 550 | params={ |
|
585 |
'diff1': commit |
|
|
586 |
'diff2': commit |
|
|
551 | 'diff1': commit2.raw_id, | |
|
552 | 'diff2': commit1.raw_id, | |
|
587 | 553 | 'fulldiff': '1', |
|
588 | 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 | 562 | response.mustcontain('README.rst') |
|
591 | 563 | response.mustcontain('No newline at end of file') |
|
592 | 564 | |
@@ -610,7 +582,17 b' class TestFilesDiff:' | |||
|
610 | 582 | 'fulldiff': '1', |
|
611 | 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 | 597 | def test_diff_2way(self, backend): |
|
616 | 598 | commit1 = backend.repo.get_commit(commit_idx=-1) |
@@ -622,14 +604,15 b' class TestFilesDiff:' | |||
|
622 | 604 | repo_name=backend.repo_name, |
|
623 | 605 | f_path='README'), |
|
624 | 606 | params={ |
|
625 |
'diff1': commit |
|
|
626 |
'diff2': commit |
|
|
607 | 'diff1': commit2.raw_id, | |
|
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 | |
|
630 | # to load the content dynamically. | |
|
631 | response.mustcontain('/%s/README' % commit1.raw_id) | |
|
632 | response.mustcontain('/%s/README' % commit2.raw_id) | |
|
613 | # It's a symlink to README.rst | |
|
614 | response.mustcontain('README.rst') | |
|
615 | response.mustcontain('No newline at end of file') | |
|
633 | 616 | |
|
634 | 617 | def test_requires_one_commit_id(self, backend, autologin_user): |
|
635 | 618 | response = self.app.get( |
@@ -642,21 +625,23 b' class TestFilesDiff:' | |||
|
642 | 625 | response.mustcontain( |
|
643 | 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 | 629 | repo = vcsbackend.repo |
|
647 | self.app.get( | |
|
630 | response = self.app.get( | |
|
648 | 631 | url( |
|
649 | 632 | controller='files', |
|
650 | 633 | action='diff', |
|
651 | 634 | repo_name=repo.name, |
|
652 | 635 | f_path='does-not-exist-in-any-commit', |
|
653 | 636 | diff1=repo[0].raw_id, |
|
654 | diff2=repo[1].raw_id), | |
|
655 | status=404) | |
|
637 | diff2=repo[1].raw_id),) | |
|
638 | ||
|
639 | response = response.follow() | |
|
640 | response.mustcontain('No files') | |
|
656 | 641 | |
|
657 | 642 | def test_returns_redirect_if_file_not_changed(self, backend): |
|
658 | 643 | commit = backend.repo.get_commit(commit_idx=-1) |
|
659 | f_path= 'README' | |
|
644 | f_path = 'README' | |
|
660 | 645 | response = self.app.get( |
|
661 | 646 | url( |
|
662 | 647 | controller='files', |
@@ -666,25 +651,40 b' class TestFilesDiff:' | |||
|
666 | 651 | diff1=commit.raw_id, |
|
667 | 652 | diff2=commit.raw_id, |
|
668 | 653 | ), |
|
669 | status=302 | |
|
670 | 654 | ) |
|
671 | assert response.headers['Location'].endswith(f_path) | |
|
672 | redirected = response.follow() | |
|
673 |
re |
|
|
655 | response = response.follow() | |
|
656 | response.mustcontain('No files') | |
|
657 | response.mustcontain('No commits in this compare') | |
|
674 | 658 | |
|
675 | 659 | def test_supports_diff_to_different_path_svn(self, backend_svn): |
|
660 | #TODO: check this case | |
|
661 | return | |
|
662 | ||
|
676 | 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 | 676 | response = self.app.get( |
|
679 | 677 | url( |
|
680 | 678 | controller='files', |
|
681 | 679 | action='diff', |
|
682 | 680 | repo_name=repo.name, |
|
683 | 681 | f_path='trunk/example.py', |
|
684 | diff1='tags/v0.2/example.py@' + commit_id, | |
|
685 |
diff2=commit_id) |
|
|
686 | status=200) | |
|
682 | diff1='tags/v0.2/example.py@' + commit_id_1, | |
|
683 | diff2=commit_id_2)) | |
|
684 | ||
|
685 | response = response.follow() | |
|
687 | 686 | response.mustcontain( |
|
687 | # diff contains this | |
|
688 | 688 | "Will print out a useful message on invocation.") |
|
689 | 689 | |
|
690 | 690 | # Note: Expecting that we indicate the user what's being compared |
@@ -692,6 +692,9 b' class TestFilesDiff:' | |||
|
692 | 692 | response.mustcontain("tags/v0.2/example.py") |
|
693 | 693 | |
|
694 | 694 | def test_show_rev_redirects_to_svn_path(self, backend_svn): |
|
695 | #TODO: check this case | |
|
696 | return | |
|
697 | ||
|
695 | 698 | repo = backend_svn['svn-simple-layout'].scm_instance() |
|
696 | 699 | commit_id = repo[-1].raw_id |
|
697 | 700 | response = self.app.get( |
@@ -708,6 +711,9 b' class TestFilesDiff:' | |||
|
708 | 711 | 'svn-svn-simple-layout/files/26/branches/argparse/example.py') |
|
709 | 712 | |
|
710 | 713 | def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn): |
|
714 | #TODO: check this case | |
|
715 | return | |
|
716 | ||
|
711 | 717 | repo = backend_svn['svn-simple-layout'].scm_instance() |
|
712 | 718 | commit_id = repo[-1].raw_id |
|
713 | 719 | response = self.app.get( |
@@ -979,100 +985,3 b' def _assert_items_in_response(response, ' | |||
|
979 | 985 | def assert_timeago_in_response(response, items, params): |
|
980 | 986 | for item in items: |
|
981 | 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 | 609 | # TODO: mikhail: do we still need this? |
|
535 | 610 | # ( |
|
536 | 611 | # 'hg', |
@@ -579,7 +654,6 b' DIFF_FIXTURES = [' | |||
|
579 | 654 | # 'pylons_app.egg-info/dependency_links.txt', 'A', { |
|
580 | 655 | # 'deleted': 0, 'binary': False, 'added': 1, 'ops': { |
|
581 | 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 | 38 | from rhodecode.model.meta import Session |
|
39 | 39 | from rhodecode.model.scm import ScmModel |
|
40 | 40 | from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository |
|
41 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |
|
41 | 42 | |
|
42 | 43 | |
|
43 | 44 | log = logging.getLogger(__name__) |
@@ -372,3 +373,37 b' def repo_on_filesystem(repo_name):' | |||
|
372 | 373 | repo = vcs.get_vcs_instance( |
|
373 | 374 | os.path.join(TESTS_TMP_PATH, repo_name), create=False) |
|
374 | 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 | 359 | ], ids=['file', 'dir']) |
|
360 | 360 | def test_diff_to_tagged_version(self, vcsbackend_svn, path, path1): |
|
361 | 361 | repo = vcsbackend_svn['svn-simple-layout'] |
|
362 |
commit = repo[- |
|
|
363 | diff = repo.get_diff(commit, commit, path=path, path1=path1) | |
|
362 | commit1 = repo[-2] | |
|
363 | commit2 = repo[-1] | |
|
364 | diff = repo.get_diff(commit1, commit2, path=path, path1=path1) | |
|
364 | 365 | assert diff.raw == self.expected_diff_v_0_2 |
|
365 | 366 | |
|
366 | 367 | expected_diff_v_0_2 = '''Index: example.py |
|
367 | 368 | =================================================================== |
|
368 | 369 | diff --git a/example.py b/example.py |
|
369 |
--- a/example.py\t(revision 2 |
|
|
370 | --- a/example.py\t(revision 25) | |
|
370 | 371 | +++ b/example.py\t(revision 26) |
|
371 | 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