Kiln » TortoiseHg » TortoiseHg
Clone URL:  
Pushed to one repository · View In Graph Contained in 1.0, 1.0.1, and 1.0.2

Merge with stable

Changeset f27f08cac337

Parents 86ad2e9d9583

Parents 1f3219626658

by Steve Borho

Changes to 34 files · Browse files at f27f08cac337 Showing diff from parent 86ad2e9d9583 1f3219626658 Diff from another changeset...

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
 
4
5
6
7
 
 
 
8
9
 
10
11
 
 
 
 
 
 
 
 
12
13
14
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
24
25
 
 
 
26
27
28
29
 
30
31
 
32
33
34
35
36
37
38
39
40
 
41
@@ -1,14 +1,41 @@
+# +# hgtk +# +# contrib/bash_completion +# +# Copyright 2009 Andreas Tscharner <andy@vis.ethz.ch> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +  _hgtk()  { - local cur prev opts + local cur prev opts cmds   - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}"   - opts="about add annotate archive blame checkout clone commit datamine diff filter email forget grep guess hgignore history ignore init incoming log merge outgoing pull push recovery remove rename repoconfig revert rollback serve shelve status strip synch unshelve update userconfig vdiff verify version" + cmds="about add annotate archive blame checkout clone commit datamine diff email explorer filter forget grep guess hgignore history ignore init incoming log merge outgoing pull push recovery remove rename repoconfig revert rollback serve shelve status strip synch unshelve update userconfig vdiff verify version"   - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + opts="-R --repository -v --verbose -q --quiet -h --help --debugger --nofork --fork --listfile" + + if [[ ${cur} == -* ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + else + COMPREPLY=( $(compgen -W "${cmds}" -- ${cur}) ) + fi + return 0  } -  complete -F _hgtk hgtk
 
58
59
60
 
 
 
 
61
62
63
 
147
148
149
 
 
150
151
152
153
 
 
 
 
 
 
 
 
 
 
 
154
155
156
157
158
159
160
161
162
163
 
58
59
60
61
62
63
64
65
66
67
 
151
152
153
154
155
156
 
 
 
157
158
159
160
161
162
163
164
165
166
167
168
 
 
 
 
 
 
169
170
171
@@ -58,6 +58,10 @@
 winmergeu.args=/e /ub /dl other /dr local $other $local $output  winmergeu.fixeol=True  winmergeu.gui=True +araxis.executable=C:\Program Files\Araxis\Araxis Merge v6.5\AraxisP4WinMrg.exe +araxis.priority=1 +araxis.args=$base $other $local $output +araxis.gui=True    ;  ; For more information about mercurial extensions, start here @@ -147,17 +151,21 @@
 ;cmd.vdiff = C:\Progra~1\TortoiseSVN\bin\TortoiseMerge.exe  ;cmd.vimdiff = gvim.exe  ;opts.vimdiff = -f '+next' '+execute "DirDiff ".argv(0)." ".argv(1)' +;cmd.araxis=C:\Program Files\Araxis\Araxis Merge v6.5\compare.exe +;opts.araxis=/wait   -[qct] -;See http://bitbucket.org/tortoisehg/stable/wiki/FAQ for details -;path="C:\Program Files\qct\qct.exe" +[tortoisehg] +; These templates will typically be applied to a repository's .hg/hgrc +; file. They are included them here for reference. +; +; Template for linking BitBucket style bug report IDs (#412) +;issue.regex = #(\d+)\b +;issue.link = http://bitbucket.org/<your project and repo>/issue/{1}/ +; +; Template for linking Mercurial style bug report IDs (issue1200) +;issue.regex = \bissue\d+\b +;issue.link = http://mercurial.selenic.com/bts/   -[hgk] -;See http://bitbucket.org/tortoisehg/stable/wiki/FAQ for details -;path={app}\scripts\hgk.cmd -;vdiff=vdiff - -;  ; The git extended diff format will properly store binary files,  ; file permission changes, and rename information that the normal  ; patch format cannot cover. However it is also not 100% compatible
 
50
51
52
53
54
55
 
 
 
 
56
57
58
 
 
59
60
61
 
50
51
52
 
 
 
53
54
55
56
57
58
 
59
60
61
62
63
@@ -50,12 +50,14 @@
 Name: shell; Description: Shell integration (overlay icons, context menu) [admin required]; Types: full; Flags: restart; Check: ShellInstallPossible    [Files] -Source: ..\build-hg\contrib\mercurial.el; DestDir: {app}/contrib -Source: ..\build-hg\contrib\vim\*.*; DestDir: {app}/contrib/vim -Source: ..\build-hg\contrib\zsh_completion; DestDir: {app}/contrib +Source: ..\build-hg\contrib\mercurial.el; DestDir: {app}\contrib +Source: ..\build-hg\contrib\vim\*.*; DestDir: {app}\contrib\vim +Source: ..\build-hg\contrib\zsh\*.*; DestDir: {app}\contrib\zsh +Source: ..\build-hg\contrib\bash\*.*; DestDir: {app}\contrib\bash  Source: ..\build-hg\contrib\hgk; DestDir: {app}/contrib  Source: ..\build-hg\contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme -Source: ..\build-hg\templates\*.*; DestDir: {app}\templates; Flags: recursesubdirs createallsubdirs +Source: ..\build-hg\mercurial\templates\*.*; DestDir: {app}\templates; Flags: recursesubdirs createallsubdirs +Source: ..\build-hg\mercurial\help\*.txt; DestDir: {app}\help; Components: help  Source: ..\build-hg\locale\*.*; DestDir: {app}\locale; Flags: recursesubdirs createallsubdirs  Source: ..\build-hg\i18n\*.*; DestDir: {app}\i18n  Source: ..\build-hg\doc\*.html; DestDir: {app}\docs; Flags: ignoreversion; Components: help
 
332
333
334
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
336
337
 
341
342
343
 
 
344
345
346
 
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
 
375
376
377
378
379
380
381
382
@@ -332,6 +332,40 @@
 patches into a repository, so this must be done on the command line with  the :command:`hg import` command.   +Message Parsing +--------------- + +New in TortoiseHg 0.10, the repository browser will detect and underline +changeset hashes and bug report identifiers inside changeset messages. These +underlined phrases are clickable links. + +Every word-boundary delimited string of 12 or 40 characters from the +range [0-9a-f] is considered a changeset link. Clicking on it in the +repository explorer will jump to the given changeset if possible. + +Issue tracker links are enabled when configured in the tortoisehg +section of the configuration files. There are two keys: issue.regex +and issue.link. The first defines the regex to match when picking up +issue numbers, while the second defines the command to run when an +issue number is recognized. + +You may include groups in issue.regex, and corresponding {n} tokens +in issue.link (where n is a non-negative integer). {0} refers to the +entire string matched by issue.regex, while {1} refers to the first +group and so on. If no {n} tokens are found in issue.link, the entire +matched string is appended instead. + +Examples:: + + BitBucket: + issue.regex = #(\d+)\b + issue.link = http://bitbucket.org/<your project and repo>/issue/{1}/ + + Mercurial: + issue.regex = \bissue\d+\b + issue.link = http://mercurial.selenic.com/bts/ + +  Keyboard navigation  -------------------   @@ -341,6 +375,8 @@
  Display visual diffs for selected changeset or file  :kbd:`Ctrl-R`   Refresh repository contents +:kbd:`Ctrl-G` + Go to a specific revision      Configurables
 
49
50
51
52
 
53
54
 
55
56
57
 
49
50
51
 
52
53
 
54
55
56
57
@@ -49,9 +49,9 @@
 # built documents.  #  # The short X.Y version. -version = '0.9' +version = '0.10'  # The full version, including alpha/beta/rc tags. -release = '0.9.1' +release = '0.10.0'    # The language for content autogenerated by Sphinx. Refer to documentation  # for a list of supported languages.
Added image
Added image
Added image
Change 1 of 1 Show Entire File setup.py Stacked
 
191
192
193
194
 
195
196
197
 
191
192
193
 
194
195
196
197
@@ -191,7 +191,7 @@
  version=version,   author='Steve Borho',   author_email='steve@borho.org', - url='http://tortoisehg.bitbucket.org', + url='http://tortoisehg.org',   description=desc,   license='GNU GPL2',   scripts=scripts,
 
9
10
11
12
13
14
15
 
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
43
44
45
 
9
10
11
 
12
13
 
14
15
16
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
20
21
22
@@ -9,37 +9,14 @@
 import os  import sys  import gtk -import threading    from tortoisehg.util.i18n import _ -from tortoisehg.util import version, paths, hglib +from tortoisehg.util import version, paths, hglib, shlib    from tortoisehg.hgtk import gtklib, hgtk   -def browse_url(url): - def start_browser(): - if os.name == 'nt': - import win32api, win32con - win32api.ShellExecute(0, "open", url, None, "", - win32con.SW_SHOW) - elif sys.platform == 'darwin': - # use Mac OS X internet config module (removed in Python 3.0) - import ic - ic.launchurl(url) - else: - try: - import gconf - client = gconf.client_get_default() - browser = client.get_string( - '/desktop/gnome/url-handlers/http/command') + '&' - os.system(browser % url) - except ImportError: - # If gconf is not found, fall back to old standard - os.system('firefox ' + url) - threading.Thread(target=start_browser).start() -  def url_handler(dialog, link, user_data): - browse_url(link) + shlib.browse_url(link)    gtk.about_dialog_set_url_hook(url_handler, None)  
Change 1 of 1 Show Entire File tortoisehg/​hgtk/​bookmark.py Stacked
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
@@ -0,0 +1,260 @@
+# bookmarkadd.py - TortoiseHg dialog to add bookmark +# +# Copyright 2007 TK Soh <teekaysoh@gmail.com> +# Copyright 2007 Steve Borho <steve@borho.org> +# Copyright 2009 Emmanuel Rosa <goaway1000@gmail.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2, incorporated herein by reference. + +import os +import gtk +import traceback + +from mercurial import hg, ui, util +from hgext import bookmarks + +from tortoisehg.util.i18n import _ +from tortoisehg.util import hglib, i18n + +from tortoisehg.hgtk import dialog, gtklib + +keep = i18n.keepgettext() + +class BookmarkAddDialog(gtk.Dialog): + """ Dialog to add bookmark to Mercurial repo """ + def __init__(self, repo, bookmark='', rev=''): + """ Initialize the Dialog """ + gtk.Dialog.__init__(self, + buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) + gtklib.set_tortoise_keys(self) + self.set_title(_('Bookmark - %s') % hglib.get_reponame(repo)) + self.set_resizable(False) + self.set_has_separator(False) + self.connect('response', self.dialog_response) + + self.repo = repo + + # add Add button + addbutton = gtk.Button(_('Add')) + addbutton.connect('clicked', lambda b: self._do_add_bookmark()) + self.action_area.pack_end(addbutton) + + # add Remove button + removebutton = gtk.Button(_('Remove')) + removebutton.connect('clicked', lambda b: self._do_rm_bookmark()) + self.action_area.pack_end(removebutton) + + # top layout table + table = gtklib.LayoutTable() + self.vbox.pack_start(table, True, True, 2) + + ## bookmark name input + self._bookmarkslist = gtk.ListStore(str) + self._bookmarklistbox = gtk.ComboBoxEntry(self._bookmarkslist, 0) + self._bookmark_input = self._bookmarklistbox.get_child() + self._bookmark_input.connect('activate', self._bookmarkinput_activated) + self._bookmark_input.set_text(bookmark) + table.add_row(_('Bookmark:'), self._bookmarklistbox, padding=False) + + ## revision input + self._rev_input = gtk.Entry() + self._rev_input.set_width_chars(12) + self._rev_input.set_text(rev) + table.add_row(_('Revision:'), self._rev_input) + + # prepare to show + self._refresh() + self._bookmarklistbox.grab_focus() + + def _refresh(self): + """ update display on dialog with recent repo data """ + self.repo.invalidate() + self._bookmarkslist.clear() + self._bookmark_input.set_text("") + + # add bookmarks to drop-down list + bookmarks = hglib.get_repo_bookmarks(self.repo) + bookmarks.sort() + for bookmarkname in bookmarks: + if bookmarkname != "tip": + self._bookmarkslist.append([bookmarkname]) + + def dialog_response(self, dialog, response_id): + if response_id == gtk.RESPONSE_CLOSE \ + or response_id == gtk.RESPONSE_DELETE_EVENT: + self.destroy() + + def _bookmarkinput_activated(self, bookmarkinput): + self._do_add_bookmark() + + def _do_add_bookmark(self): + # gather input data + name = self._bookmark_input.get_text() + rev = self._rev_input.get_text() + + # verify input + if name == '': + dialog.error_dialog(self, _('Bookmark input is empty'), + _('Please enter bookmark name')) + self._bookmark_input.grab_focus() + return False + + # add bookmark to repo + try: + self._add_hg_bookmark(name, rev) + dialog.info_dialog(self, _('Bookmarking completed'), + _('Bookmark "%s" has been added') % name) + self._refresh() + except util.Abort, inst: + dialog.error_dialog(self, _('Error in bookmarking'), str(inst)) + return False + except: + dialog.error_dialog(self, _('Error in bookmarking'), + traceback.format_exc()) + return False + + def _do_rm_bookmark(self): + # gather input data + name = self._bookmark_input.get_text() + + # verify input + if name == '': + dialog.error_dialog(self, _('Bookmark name is empty'), + _('Please select bookmark name to remove')) + self._bookmark_input.grab_focus() + return False + + try: + self._rm_hg_bookmark(name) + dialog.info_dialog(self, _('Bookmarking completed'), + _('Bookmark "%s" has been removed') % name) + self._refresh() + except util.Abort, inst: + dialog.error_dialog(self, _('Error in bookmarking'), str(inst)) + return False + except: + dialog.error_dialog(self, _('Error in bookmarking'), + traceback.format_exc()) + return False + + def _add_hg_bookmark(self, name, revision): + if name in hglib.get_repo_bookmarks(self.repo): + raise util.Abort(_('a bookmark named "%s" already exists') % name) + + bookmarks.bookmark(ui=ui.ui(), + repo=self.repo, + rev=revision, + mark=name) + + def _rm_hg_bookmark(self, name): + if not name in hglib.get_repo_bookmarks(self.repo): + raise util.Abort(_("Bookmark '%s' does not exist") % name) + + bookmarks.bookmark(ui=ui.ui(), + repo=self.repo, + mark=name, + delete=True) + +class BookmarkRenameDialog(gtk.Dialog): + """ Dialog to rename a bookmark """ + def __init__(self, repo, bookmark='', rev=''): + """ Initialize the Dialog """ + gtk.Dialog.__init__(self, + buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) + gtklib.set_tortoise_keys(self) + self.set_title(_('Bookmark - %s') % hglib.get_reponame(repo)) + self.set_resizable(False) + self.set_has_separator(False) + self.connect('response', self.dialog_response) + + self.repo = repo + + # add Rename button + renamebutton = gtk.Button(_('Rename')) + renamebutton.connect('clicked', lambda b: self._do_rename_bookmark()) + self.action_area.pack_end(renamebutton) + + # top layout table + table = gtklib.LayoutTable() + self.vbox.pack_start(table, True, True, 2) + + ## bookmark name input + self._bookmarkslist = gtk.ListStore(str) + self._bookmarklistbox = gtk.ComboBoxEntry(self._bookmarkslist, 0) + self._bookmark_input = self._bookmarklistbox.get_child() + self._bookmark_input.connect('activate', self._bookmarkinput_activated) + self._bookmark_input.set_text(bookmark) + table.add_row(_('Bookmark:'), self._bookmarklistbox, padding=False) + + ## revision input + self._name_input = gtk.Entry() + table.add_row(_('New name:'), self._name_input) + + # prepare to show + self._refresh() + self._bookmarklistbox.grab_focus() + + def _refresh(self): + """ update display on dialog with recent repo data """ + self.repo.invalidate() + self._bookmarkslist.clear() + self._bookmark_input.set_text("") + + # add bookmarks to drop-down list + bookmarks = hglib.get_repo_bookmarks(self.repo) + bookmarks.sort() + for bookmarkname in bookmarks: + if bookmarkname == "tip": + continue + self._bookmarkslist.append([bookmarkname]) + + def dialog_response(self, dialog, response_id): + if response_id == gtk.RESPONSE_CLOSE \ + or response_id == gtk.RESPONSE_DELETE_EVENT: + self.destroy() + + def _bookmarkinput_activated(self, bookmarkinput): + self._do_rename_bookmark() + + def _do_rename_bookmark(self): + # gather input data + name = self._bookmark_input.get_text() + new_name = self._name_input.get_text() + + # verify input + if name == '': + dialog.error_dialog(self, _('Bookmark input is empty'), + _('Please enter bookmark name')) + self._bookmark_input.grab_focus() + return False + + if new_name == '': + dialog.error_dialog(self, _('Bookmark new name input is empty'), + _('Please enter new bookmark name')) + self._bookmark_input.grab_focus() + return False + + # rename bookmark + try: + self._rename_hg_bookmark(name, new_name) + dialog.info_dialog(self, _('Bookmarking completed'), + _('Bookmark "%s" has been renamed to "%s"') % + (name, new_name)) + self._refresh() + except util.Abort, inst: + dialog.error_dialog(self, _('Error in bookmarking'), str(inst)) + return False + except: + dialog.error_dialog(self, _('Error in bookmarking'), + traceback.format_exc()) + return False + + def _rename_hg_bookmark(self, name, new_name): + if new_name in hglib.get_repo_bookmarks(self.repo): + raise util.Abort(_('a bookmark named "%s" already exists') % new_name) + bookmarks.bookmark(ui=ui.ui(), + repo=self.repo, + mark=new_name, + rename=name) +
 
6
7
8
 
9
10
11
 
14
15
16
17
 
18
19
20
 
26
27
28
 
 
 
 
 
 
 
 
29
30
31
 
92
93
94
 
95
96
97
98
99
 
261
262
263
264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
266
267
 
358
359
360
361
 
362
363
 
364
365
366
 
608
609
610
 
611
612
613
 
635
636
637
638
 
 
639
640
641
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
643
644
 
646
647
648
649
650
651
652
653
 
654
655
656
 
710
711
712
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
714
715
 
6
7
8
9
10
11
12
 
15
16
17
 
18
19
20
21
 
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 
101
102
103
104
105
 
106
107
108
 
270
271
272
 
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
 
387
388
389
 
390
391
 
392
393
394
395
 
637
638
639
640
641
642
643
 
665
666
667
 
668
669
670
 
 
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
 
706
707
708
 
709
 
710
711
712
713
714
715
 
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
@@ -6,6 +6,7 @@
 # GNU General Public License version 2, incorporated herein by reference.    import os +import re  import gtk  import gobject  import pango @@ -14,7 +15,7 @@
 from mercurial import cmdutil, util, patch, mdiff    from tortoisehg.util.i18n import _ -from tortoisehg.util import shlib, hglib +from tortoisehg.util import shlib, hglib, paths    from tortoisehg.hgtk import csinfo, gdialog, gtklib, hgcmd, statusbar   @@ -26,6 +27,14 @@
  self.glog_parent = None   self.bfile = None   + # initialize changeset/issue tracker link regex and dict + match = r'(\b[0-9a-f]{12}(?:[0-9a-f]{28})?\b)' + issue = repo.ui.config('tortoisehg', 'issue.regex') + if issue: + match = r'%s|(%s)' % (match, issue) + self.bodyre = re.compile(match) + self.issuedict = dict() +   def get_title(self):   title = _('%s changeset ') % self.get_reponame()   rev = self.opts['rev'] @@ -92,8 +101,8 @@
  if len(parents) == 2:   # deferred adding of parent check button   if not self.parent_button.parent: + self.parent_box.pack_start(self.parent_button, False, False)   self.parent_box.pack_start(gtk.HSeparator(), False, False) - self.parent_box.pack_start(self.parent_button, False, False)   self.parent_box.show_all()     # show parent box @@ -261,7 +270,27 @@
  buf = self._buffer   buf.set_text('')   eob = buf.get_end_iter() - buf.insert(eob, desc.rstrip('\n\r') + '\n\n') + desc = desc.rstrip('\n\r') + + pos = 0 + self.issuedict.clear() + for m in self.bodyre.finditer(desc): + a, b = m.span() + if a > pos: + buf.insert(eob, desc[pos:a]) + pos = b + groups = m.groups() + link = groups[0] + if link: + buf.insert_with_tags_by_name(eob, link, 'csetlink') + else: + link = groups[1] + if len(groups) > 2: + self.issuedict[link] = groups[1:] + buf.insert_with_tags_by_name(eob, link, 'issuelink') + if pos < len(desc): + buf.insert(eob, desc[pos:]) + buf.insert(eob, '\n\n')     def append_diff(self, wfile):   if not wfile: @@ -358,9 +387,9 @@
  offset += len(txt.decode('utf-8'))   for l1 in difflines[1:]:   l = hglib.toutf(l1) - if l.startswith('+++'): + if l.startswith('--- '):   continue - if l.startswith('---'): + if l.startswith('+++ '):   continue   if l.startswith('@@'):   tag = 'blue' @@ -608,6 +637,7 @@
    ## file list   filelist_tree = gtk.TreeView() + filelist_tree.set_headers_visible(False)   filesel = filelist_tree.get_selection()   filesel.connect('changed', self.filelist_rowchanged)   self._filesel = filesel @@ -635,10 +665,40 @@
  gobject.TYPE_STRING, # filename   )   filelist_tree.set_model(self._filelist) - column = gtk.TreeViewColumn(_('Stat'), gtk.CellRendererText(), text=0) + + column = gtk.TreeViewColumn()   filelist_tree.append_column(column) - column = gtk.TreeViewColumn(_('Files'), gtk.CellRendererText(), text=1) - filelist_tree.append_column(column) + + iconcell = gtk.CellRendererPixbuf() + filecell = gtk.CellRendererText() + + column.pack_start(iconcell, expand=False) + column.pack_start(filecell, expand=False) + column.add_attribute(filecell, 'text', 1) + + iconw, iconh = gtk.icon_size_lookup(gtk.ICON_SIZE_SMALL_TOOLBAR) + + def get_pixbuf(iconfilename): + iconpath = paths.get_tortoise_icon(iconfilename) + return gtk.gdk.pixbuf_new_from_file_at_size( + iconpath, iconw, iconh) + + addedpixbuf = get_pixbuf('fileadd.ico') + removedpixbuf = get_pixbuf('filedelete.ico') + modifiedpixbuf = get_pixbuf('filemodify.ico') + + def cell_seticon(column, cell, model, iter): + state = model.get_value(iter, 0) + pixbuf = None + if state == 'A': + pixbuf = addedpixbuf + elif state == 'R': + pixbuf = removedpixbuf + elif state == 'M': + pixbuf = modifiedpixbuf + cell.set_property('pixbuf', pixbuf) + + column.set_cell_data_func(iconcell, cell_seticon)     list_frame = gtk.Frame()   list_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN) @@ -646,11 +706,10 @@
  scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)   scroller.add(filelist_tree)   flbox = gtk.VBox() - flbox.pack_start(scroller)   list_frame.add(flbox) -   self.parent_box = gtk.VBox()   flbox.pack_start(self.parent_box, False, False) + flbox.pack_start(scroller)     btn = gtk.CheckButton(_('Diff to second Parent'))   btn.connect('toggled', self.parent_toggled) @@ -710,6 +769,56 @@
  weight=pango.WEIGHT_BOLD))   tag_table.add(make_texttag('yellowbg', background='yellow'))   + issuelink_tag = make_texttag('issuelink', foreground='blue', + underline=pango.UNDERLINE_SINGLE) + issuelink_tag.connect('event', self.issuelink_event) + tag_table.add(issuelink_tag) + csetlink_tag = make_texttag('csetlink', foreground='blue', + underline=pango.UNDERLINE_SINGLE) + csetlink_tag.connect('event', self.csetlink_event) + tag_table.add(csetlink_tag) + + def issuelink_event(self, tag, widget, event, liter): + if event.type != gtk.gdk.BUTTON_RELEASE: + return + text = self.get_link_text(tag, widget, liter) + if not text: + return + link = self.repo.ui.config('tortoisehg', 'issue.link') + if link: + groups = self.issuedict.get(text, [text]) + link, num = re.subn(r'\{(\d+)\}', lambda m: + groups[int(m.group(1))], link) + if not num: + link += text + shlib.browse_url(link) + + def csetlink_event(self, tag, widget, event, liter): + if event.type != gtk.gdk.BUTTON_RELEASE: + return + text = self.get_link_text(tag, widget, liter) + if not text: + return + try: + rev = self.repo[text].rev() + if self.graphview: + self.graphview.set_revision_id(rev, load=True) + else: + self.load_details(rev) + except RepoError: + pass + + def get_link_text(self, tag, widget, liter): + text_buffer = widget.get_buffer() + beg = liter.copy() + while not beg.begins_tag(tag): + beg.backward_char() + end = liter.copy() + while not end.ends_tag(tag): + end.forward_char() + text = text_buffer.get_text(beg, end) + return text +   def file_button_release(self, widget, event):   if event.button == 3 and not (event.state & (gtk.gdk.SHIFT_MASK |   gtk.gdk.CONTROL_MASK)):
 
51
52
53
54
 
55
56
57
 
74
75
76
77
 
 
 
 
 
 
 
 
78
79
80
 
85
86
87
88
 
89
90
91
 
206
207
208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
210
211
 
266
267
268
269
 
270
271
272
 
51
52
53
 
54
55
56
57
 
74
75
76
 
77
78
79
80
81
82
83
84
85
86
87
 
92
93
94
 
95
96
97
98
 
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
 
288
289
290
 
291
292
293
294
@@ -51,7 +51,7 @@
  elif len(repos):   srcpath = repos[0]   - def createcombo(path, label, title): + def createcombo(path, label, title, bundle=False):   # comboentry   model = gtk.ListStore(str)   combo = gtk.ComboBoxEntry(model, 0) @@ -74,7 +74,14 @@
  browse = gtk.Button(_('Browse...'))   browse.connect('clicked', self.browse_clicked, title, entry)   - table.add_row(label, combo, 0, browse) + if bundle: + # bundle button + bundlebtn = gtk.Button(_('Bundle...')) + bundlebtn.connect('clicked', self.bundle_clicked, + _('Select a Mercurial Bundle'), entry) + table.add_row(label, combo, 0, browse, bundlebtn) + else: + table.add_row(label, combo, 0, browse)     return model, combo   @@ -85,7 +92,7 @@
  ## comboentry for source paths   self.srclist, srccombo = createcombo(srcpath,   _('Source path:'), - _('Select Source Folder')) + _('Select Source Folder'), True)   self.srcentry = srccombo.get_child()     ## add pre-defined src paths to pull-down list @@ -206,6 +213,21 @@
  if res:   entry.set_text(res)   + def bundle_clicked(self, button, title, entry): + path = entry.get_text() + if os.path.isdir(path): + initial = path + else: + initial = os.path.dirname(path) + + res = gtklib.NativeSaveFileDialogWrapper( + initial=initial, + title=title, + filter= ((_('Mercurial bundles'), '*.hg'),), + open=True).run() + if res: + entry.set_text(res) +   def checkbutton_toggled(self, checkbutton, entry):   state = checkbutton.get_active()   entry.set_sensitive(state) @@ -266,7 +288,7 @@
    def clone(self):   # gather input data - src = self.srcentry.get_text() + src = self.srcentry.get_text().strip()   dest = self.destentry.get_text() or os.path.basename(src)   remotecmd = self.remotecmdentry.get_text()   rev = self.reventry.get_text()
 
858
859
860
861
862
863
864
865
866
867
868
869
870
 
858
859
860
 
 
 
 
 
 
 
861
862
863
@@ -858,13 +858,6 @@
  _('Errors during rollback!'), self).run()     - def changelog_clicked(self, toolbutton, data=None): - from tortoisehg.hgtk import history - dlg = history.run(self.ui) - dlg.display() - return True - -   def should_addremove(self, files):   if self.test_opt('addremove'):   return True
 
18
19
20
21
 
22
23
24
 
387
388
389
390
391
 
392
393
394
 
18
19
20
 
21
22
23
24
 
387
388
389
 
 
390
391
392
393
@@ -18,7 +18,7 @@
 from mercurial import cmdutil, util, ui, hg, commands    from tortoisehg.util.i18n import _ -from tortoisehg.util import settings, hglib, paths +from tortoisehg.util import settings, hglib, paths, shlib    from tortoisehg.hgtk import gtklib   @@ -387,8 +387,7 @@
  return   if not url.startswith('http'):   url = 'http://tortoisehg.org/manual/0.9/' + url - from tortoisehg.hgtk import about - about.browse_url(url) + shlib.browse_url(url)     def launch(self, item, app):   import sys
 
68
69
70
 
 
71
72
73
 
318
319
320
321
 
322
323
324
 
68
69
70
71
72
73
74
75
 
320
321
322
 
323
324
325
326
@@ -68,6 +68,8 @@
  window.connect('thg-close', thgclose)   window.connect('thg-exit', thgexit)   + return accelgroup, mod +  def thgexit(window):   if thgclose(window):   gobject.idle_add(hgtk.thgexit, window) @@ -318,7 +320,7 @@
  return fname     def runCompatible(self): - dialog = gtk.FileChooserDialog(title=None, + dialog = gtk.FileChooserDialog(title=self.title,   action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,   buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,   gtk.STOCK_OPEN,gtk.RESPONSE_OK))
 
222
223
224
225
226
227
228
 
 
 
 
229
230
231
232
233
234
235
 
 
 
 
 
 
236
237
238
 
324
325
326
327
328
 
 
329
330
331
 
332
333
334
335
336
 
337
338
339
 
376
377
378
379
 
380
381
382
383
384
 
385
386
387
 
409
410
411
412
 
413
414
415
 
418
419
420
421
 
422
423
424
 
573
574
575
576
 
577
578
579
 
587
588
589
590
 
591
592
593
 
597
598
599
 
600
601
602
 
603
604
605
606
607
608
609
610
611
612
613
 
 
 
 
 
614
615
616
 
637
638
639
640
 
641
642
643
644
 
645
646
 
647
648
649
650
651
652
 
653
654
655
656
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
223
224
 
 
 
 
225
226
227
228
229
 
 
 
 
 
 
230
231
232
233
234
235
236
237
238
 
324
325
326
 
 
327
328
329
330
 
331
332
333
334
335
 
336
337
338
339
 
376
377
378
 
379
380
381
382
 
 
383
384
385
386
 
408
409
410
 
411
412
413
414
 
417
418
419
 
420
421
422
423
 
572
573
574
 
575
576
577
578
 
586
587
588
 
589
590
591
592
 
596
597
598
599
600
601
 
602
603
604
 
 
 
 
 
 
 
 
 
605
606
607
608
609
610
611
612
 
633
634
635
 
636
637
638
639
 
640
641
 
642
643
644
645
646
647
 
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
@@ -222,17 +222,17 @@
    def __init__(self, style=STYLE_NORMAL, tooltips=None, logsize=None):   """ - style: String. Predefined constans of CmdWidget style. Two styles; - STYLE_NORMAL (progress bar + popup log viewer) and - STYLE_COMPACT (progress bar + embedded log viewer) are - availabled. Default: STYLE_NORMAL. + style: String. Predefined constans of CmdWidget style. Two styles, + STYLE_NORMAL (progress bar + popup log viewer) and + STYLE_COMPACT (progress bar + embedded log viewer) are + available. Default: STYLE_NORMAL.   tooltips: Reference. gtk.Tooltips instance to show tooltips of several - buttons. If you omit, it will create a new instance of - gtk.Tooltips. Default: None. - logsize: Tuple or list contains 2 numbers. Specify the size of - embedded log viewer. size[0] = width, size[1] = height. - If you pass -1 as width or height size, it will be change to - *natual* size of the widget. Default: tuple(-1, 180). + buttons. If omitted, a new instance of gtk.Tooltips will be + created. Default: None. + logsize: Tuple or list containing two numbers. Specify the size of the + embedded log viewer. size[0] = width, size[1] = height. + If you pass -1 as width or height size, it will be set to + the natural size of the widget. Default: tuple(-1, 180).   """   gtk.VBox.__init__(self)   @@ -324,16 +324,16 @@
  def execute(self, cmdline, callback, *args, **kargs):   """   Execute passed command line using 'hgthread'. - When the command terminated, callback function is called - with return code. + When the command terminates, the callback function is invoked + with its return code.     cmdline: command line string. - callback: function called after terminated the thread. + callback: function invoked after the command terminates.     def callback(returncode, useraborted, ...)     returncode: See the description of 'hgthread'. - useraborted: Indicates whether the thread is aborted by user. + useraborted: Whether the command was aborted by the user.   """   if self.hgthread:   return @@ -376,12 +376,11 @@
    def set_result(self, text, style=None):   """ - Put text message and icon to the progress bar. + Put text message and icon in the progress bar.     style: String. If passed 'succeed', 'green' or 'ok', green   text and succeed icon will be shown. If passed 'error', - 'failed', 'fail' or 'red' are passed, red text and error - icon will be shown. + 'failed', 'fail' or 'red', red text and error icon will be shown.   """   markup = '<span foreground="%s" weight="%s">%%s</span>'   if style in ('succeed', 'green', 'ok'): @@ -409,7 +408,7 @@
  def get_pbar(self):   """   Return 'visible' property of the progress bar box. - If not exists progress bar, it always returns False. + If there is no progress bar, it returns False.   """   if hasattr(self, 'progbox'):   return self.progbox.get_property('visible') @@ -418,7 +417,7 @@
  def set_buttons(self, log=None, stop=None, close=None):   """   Set visible properties of buttons on the progress bar box. - If omitted all argments, it does nothing. + If all arguments are omitted, it does nothing.     log: if True, log button is shown. (default: None)   stop: if True, stop button is shown. (default: None) @@ -573,7 +572,7 @@
    def append(self, text, error=False):   """ - Insert the text to the end of TextView. + Insert text at the end of the TextView.     text: string you want to append.   error: if True, append text with 'error' tag. (default: False) @@ -587,7 +586,7 @@
    def clear(self):   """ - Clear all text in TextView. + Clear all text in the TextView.   """   self.buffer.delete(self.buffer.get_start_iter(),   self.buffer.get_end_iter()) @@ -597,20 +596,17 @@
  def __init__(self, title=_('Command Log')):   gtk.Window.__init__(self, type=gtk.WINDOW_TOPLEVEL)   gtklib.set_tortoise_icon(self, 'hg.ico') + accelgroup, mod = gtklib.set_tortoise_keys(self)   self.set_title(title)   self.set_default_size(320, 240) - self.connect('delete-event', self.delete_event) + self.connect('delete-event', self.should_live)     # accelerators - accelgroup = gtk.AccelGroup() - self.add_accel_group(accelgroup) - mod = gtklib.get_thg_modifier() - for key, modifier in (gtk.accelerator_parse(mod+'w'), - gtk.accelerator_parse(mod+'q'), - gtk.accelerator_parse('Escape')): - self.add_accelerator('thg-close', accelgroup, key, - modifier, gtk.ACCEL_VISIBLE) - self.connect('thg-close', self.delete_event) + key, modifier = gtk.accelerator_parse('Escape') + self.add_accelerator('thg-close', accelgroup, key, modifier, + gtk.ACCEL_VISIBLE) + self.connect('thg-close', self.should_live) + self.connect('thg-exit', self.should_live)     # log viewer   self.log = CmdLogWidget() @@ -637,20 +633,183 @@
  """   Set hook function.   - hook: the function called on closing this dialog. + hook: the function called when this dialog is closed.     def close_hook(dialog)   - where 'dialog' is the instance of CmdLogDialog class. + where 'dialog' is an instance of the CmdLogDialog class.   The hook function should return True or False. - By returning True, you can prevent closing/hiding the dialog. + If True is returned, closing the dialog is prevented.   """   self.close_hook = hook     ### signal handlers ###   - def delete_event(self, *args): + def should_live(self, *args):   if hasattr(self, 'close_hook'):   if self.close_hook(self):   self.hide()   return True + +# Structured log types for CmdRunner +LOG_NORMAL = 0 +LOG_ERROR = 1 + +class CmdRunner(object): + """ + Interactive command runner without GUI. + + By default, there is no GUI (as opposed to CmdDialog). + If user interaction is needed (e.g. HTTPS auth), a simple + input dialog will be shown. + """ + def __init__(self): + self.hgthread = None + + self.dlg = CmdLogDialog() + def close_hook(dialog): + self.show_log(False) + return False + self.dlg.set_close_hook(close_hook) + + self.clear_buffers() + + ### public functions ### + + def execute(self, cmdline, callback, *args, **kargs): + """ + Execute passed command line using 'hgthread'. + When the command terminates, the callback function is invoked + with its return code. + + cmdline: command line string. + callback: function invoked after the command terminates. + + def callback(returncode, useraborted, ...) + + returncode: See the description of 'hgthread'. + useraborted: Whether the command was aborted by the user. + + return: True if the command was started, + False if a command is already running. + """ + if self.hgthread: + return False + + # clear previous logs + self.clear_buffers() + + # thread start + self.hgthread = hgthread.HgThread(cmdline[1:]) + self.hgthread.start() + gobject.timeout_add(10, self.process_queue, callback, args, kargs) + + return True + + def is_alive(self): + """ + Return whether the thread is alive. + """ + return self.hgthread and self.hgthread.isAlive() + + def stop(self): + """ + Terminate the thread forcibly. + """ + if self.hgthread: + self.hgthread.terminate() + + def get_buffer(self): + """ + Return buffer containing all messages. + + Note that the buffer will be cleared when the 'execute' method + is called, so you need to store this before next execution. + """ + return ''.join([chunk[0] for chunk in self.buffer]) + + def get_raw_buffer(self): + """ + Return structured buffer. + + Note that the buffer will be cleared when the 'execute' method + is called, so you need to store this before next execution. + """ + return self.buffer + + def get_msg_buffer(self): + """ + Return buffer with regular messages. + + Note that the buffer will be cleared when the 'execute' method + is called, so you need to store this before next execution. + """ + return ''.join([chunk[0] for chunk in self.buffer \ + if chunk[1] == LOG_NORMAL]) + + def get_err_buffer(self): + """ + Return buffer with error messages. + + Note that the buffer will be cleared when the 'execute' method + is called, so you need to store this before next execution. + """ + return ''.join([chunk[0] for chunk in self.buffer \ + if chunk[1] == LOG_ERROR]) + + def set_title(self, title): + """ + Set the title of the command log window. + """ + self.dlg.set_title(title) + + ### internal use functions ### + + def clear_buffers(self): + """ + Clear both message and error buffers. + """ + self.buffer = [] + self.dlg.log.clear() + + def show_log(self, visible=True): + if visible: + if self.dlg.get_property('visible'): + self.dlg.present() + else: + self.dlg.show_all() + else: + self.dlg.hide() + + def process_queue(self, callback, args, kargs): + # process queue + self.hgthread.process_dialogs() + + # receive messages from queue + while self.hgthread.getqueue().qsize(): + try: + msg = hglib.toutf(self.hgthread.getqueue().get(0)) + self.buffer.append((msg, LOG_NORMAL)) + self.dlg.log.append(msg) + except Queue.Empty: + pass + while self.hgthread.geterrqueue().qsize(): + try: + msg = hglib.toutf(self.hgthread.geterrqueue().get(0)) + self.buffer.append((msg, LOG_ERROR)) + self.dlg.log.append(msg, error=True) + except Queue.Empty: + pass + + # check thread state + if not self.hgthread.isAlive(): + returncode = self.hgthread.return_code() + self.hgthread = None + if len(self.get_err_buffer()) > 0: + self.show_log(True) + def call_callback(): + callback(returncode, self.get_buffer(), *args, **kargs) + gtklib.idle_add_single_call(call_callback) + return False # Stop polling this function + else: + return True # Continue polling
 
15
16
17
 
18
19
20
 
24
25
26
27
 
28
29
 
30
31
 
 
32
33
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
36
37
 
53
54
55
 
56
57
58
 
216
217
218
 
 
219
220
221
 
227
228
229
230
 
231
232
233
 
368
369
370
371
 
372
373
374
 
462
463
464
465
466
467
468
469
470
471
472
473
474
475
 
 
476
477
478
479
480
481
482
 
 
 
483
484
485
 
734
735
736
737
738
739
740
741
742
743
 
744
745
746
 
 
 
 
 
 
 
747
748
749
 
759
760
761
 
 
 
 
 
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
 
 
 
791
792
793
794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
796
797
 
843
844
845
846
 
847
848
849
 
937
938
939
 
 
 
940
 
941
942
 
943
944
945
 
995
996
997
 
998
999
1000
 
 
 
1001
 
 
 
 
 
 
 
 
 
 
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
 
 
 
 
 
 
 
 
 
 
 
 
 
1081
1082
1083
 
1134
1135
1136
 
 
 
 
 
 
 
 
 
1137
1138
1139
 
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1228
1229
1230
 
1254
1255
1256
1257
1258
 
 
1259
1260
1261
1262
1263
1264
 
 
1265
1266
1267
 
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1333
1334
1335
 
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1363
 
 
 
 
 
 
 
 
 
1364
1365
1366
 
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1422
1423
1424
 
1439
1440
1441
 
 
 
1442
1443
1444
 
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
 
1734
1735
1736
1737
1738
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1739
1740
1741
 
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1794
1795
 
 
 
1796
1797
1798
 
15
16
17
18
19
20
21
 
25
26
27
 
28
29
 
30
31
 
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
 
137
138
139
140
141
142
143
 
301
302
303
304
305
306
307
308
 
314
315
316
 
317
318
319
320
 
455
456
457
 
458
459
460
461
 
549
550
551
 
 
 
552
 
 
553
554
555
 
 
556
557
558
559
560
561
562
563
 
564
565
566
567
568
569
 
818
819
820
 
 
 
 
 
 
 
821
822
823
 
824
825
826
827
828
829
830
831
832
833
 
843
844
845
846
847
848
849
850
851
852
853
854
855
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
 
962
963
964
 
965
966
967
968
 
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
 
1119
1120
1121
1122
1123
1124
 
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
 
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
 
1290
1291
1292
 
 
 
1293
1294
1295
1296
1297
1298
 
 
 
 
 
 
 
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
 
1348
1349
1350
 
 
1351
1352
1353
1354
1355
1356
1357
 
1358
1359
1360
1361
1362
 
1413
1414
1415
 
 
 
 
 
 
 
 
 
 
 
 
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
 
1455
1456
1457
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
 
1535
1536
1537
 
 
 
 
 
 
 
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
 
1577
1578
1579
1580
1581
1582
1583
1584
1585
 
1864
1865
1866
 
 
 
 
 
 
 
 
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
 
1962
1963
1964
 
 
 
 
 
 
 
 
 
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
 
2021
2022
2023
2024
2025
2026
@@ -15,6 +15,7 @@
 import atexit    from mercurial import ui, hg, cmdutil, commands, extensions, util, match, url +from mercurial import hbisect    from tortoisehg.util.i18n import _  from tortoisehg.util import hglib @@ -24,14 +25,97 @@
 from tortoisehg.hgtk import gdialog, gtklib, hgcmd, gorev, thgstrip  from tortoisehg.hgtk import backout, status, hgemail, tagadd, update, merge  from tortoisehg.hgtk import archive, changeset, thgconfig, thgmq, histdetails -from tortoisehg.hgtk import statusbar +from tortoisehg.hgtk import statusbar, bookmark   -def create_menu(label, callback): +def create_menu(label, callback=None):   menuitem = gtk.MenuItem(label, True) - menuitem.connect('activate', callback) + if callback: + menuitem.connect('activate', callback)   menuitem.set_border_width(1)   return menuitem   +def create_submenu(label, menu): + m = create_menu(label) + m.set_submenu(menu) + return m + +class FilterBox(gtklib.SlimToolbar): + 'Filter Toolbar for repository log' + + def __init__(self, tooltips, filter_mode, branch_names): + gtklib.SlimToolbar.__init__(self, tooltips) + self.filter_mode = filter_mode + + self.all = gtk.RadioButton(None, _('All')) + self.all.set_active(True) + self.append_widget(self.all, padding=0) + + self.tagged = gtk.RadioButton(self.all, _('Tagged')) + self.append_widget(self.tagged, padding=0) + + self.ancestry = gtk.RadioButton(self.all, _('Ancestry')) + self.append_widget(self.ancestry, padding=0) + + self.parents = gtk.RadioButton(self.all, _('Parents')) + self.append_widget(self.parents, padding=0) + + self.heads = gtk.RadioButton(self.all, _('Heads')) + self.append_widget(self.heads, padding=0) + + self.merges = gtk.RadioButton(self.all, _('Merges')) + self.append_widget(self.merges, padding=0) + + self.hidemerges = gtk.CheckButton(_('Hide Merges')) + self.append_widget(self.hidemerges, padding=0) + + self.branches = gtk.RadioButton(self.all) + tooltips.set_tip(self.branches, _('Branch Filter')) + self.branches.set_sensitive(False) + self.append_widget(self.branches, padding=0) + + self.branchcombo = gtk.combo_box_new_text() + self.branchcombo.append_text(_('Branches...')) + for name in branch_names: + self.branchcombo.append_text(hglib.toutf(name)) + self.branchcombo.set_active(0) + self.append_widget(self.branchcombo, padding=0) + + self.custombutton = gtk.RadioButton(self.all) + tooltips.set_tip(self.custombutton, _('Custom Filter')) + self.custombutton.set_sensitive(False) + self.append_widget(self.custombutton, padding=0) + + self.filtercombo = gtk.combo_box_new_text() + self.filtercombo_entries = (_('Rev Range'), _('File Patterns'), + _('Keywords'), _('Date'), _('User')) + for f in self.filtercombo_entries: + self.filtercombo.append_text(f) + if (self.filter_mode >= len(self.filtercombo_entries) or + self.filter_mode < 0): + self.filter_mode = 1 + self.filtercombo.set_active(self.filter_mode) + self.append_widget(self.filtercombo, padding=0) + + searchlist = gtk.ListStore(int, # filtercombo value + str, # search string (utf-8) + str) # mode string (utf-8) + entrycombo = gtk.ComboBoxEntry(searchlist, 1) + cell = gtk.CellRendererText() + entrycombo.pack_end(cell, False) + entrycombo.add_attribute(cell, 'text', 2) + entry = entrycombo.child + self.entrycombo = entrycombo + self.entry = entry + self.append_widget(entrycombo, expand=True, padding=0) + + + def connect(self, detailed_signal, handler, *opts): + '''Connect an external signal handler to an internal widget + Signal format is '[widget_name]_[signal]'.''' + widget_name, signal = detailed_signal.split('_') + widget = self.__dict__[widget_name] + widget.connect(signal, handler, *opts) +  class GLog(gdialog.GDialog):   'GTK+ based dialog for displaying repository logs'   def init(self): @@ -53,6 +137,7 @@
  self.revrange = None   self.forcepush = False   self.bundle_autoreject = False + self.runner = hgcmd.CmdRunner()   os.chdir(self.repo.root)     # Load extension support for commands which need it @@ -216,6 +301,8 @@
  func=self.push_clicked, icon=gtk.STOCK_GOTO_TOP),   dict(text=_('Email...'), name='email',   func=self.email_clicked, icon=gtk.STOCK_GOTO_LAST), + dict(text=_('Stop'), name='stop', sensitive=False, + func=self.stop_clicked, icon=gtk.STOCK_STOP),   dict(text='----'),   dict(text=_('Add Bundle...'), name='add-bundle',   sensitive=not bool(self.bfile), @@ -227,7 +314,7 @@
  sensitive=bool(self.bfile),   func=self.reject_clicked, icon=gtk.STOCK_DIALOG_ERROR),   dict(text='----'), - dict(name='use-proxy-server', text=_('Use proxy server'), + dict(text=_('Use proxy server'), name='use-proxy-server',   ascheck=True, func=toggle_proxy),   dict(text=_('Force push'), ascheck=True, func=toggle_force),   ]) @@ -368,7 +455,7 @@
  text = entry.get_text()   if not text:   return - row = [mode, text, combo.get_active_text()] + row = [mode, text, combo.get_active_text()]   model = self.entrycombo.get_model()   for r in model:   if r[0] == row[0] and r[1] == row[1]: @@ -462,24 +549,21 @@
  self.pats = []     opts = self.opts - if 'bundle' in opts: - self.set_bundlefile(opts['bundle']) - self.bundle_autoreject = True   if opts['filehist']: - file = opts['filehist'] - opts['pats'] = [file]   self.custombutton.set_active(True)   self.filter = 'custom'   self.filtercombo.set_active(1) - self.filterentry.set_text(file) - self.filter_entry_activated(self.filterentry, self.filtercombo) + self.filterentry.set_text(opts['filehist']) + opts['pats'] = [opts['filehist']]   elif self.pats:   self.custombutton.set_active(True)   self.filter = 'custom'   self.filtercombo.set_active(1)   self.filterentry.set_text(', '.join(self.pats))   opts['pats'] = self.pats - self.filter_entry_activated(self.filterentry, self.filtercombo) + if 'bundle' in opts: + self.set_bundlefile(opts['bundle']) + self.bundle_autoreject = True   else:   self.reload_log(**opts)   @@ -734,16 +818,16 @@
  m.append(create_menu(_('_Update...'), self.checkout))   cmenu_merge = create_menu(_('_Merge with...'), self.domerge)   m.append(cmenu_merge) - m.append_sep() - m.append(create_menu(_('_Export Patch...'), self.export_patch)) - m.append(create_menu(_('E_mail Patch...'), self.email_patch)) - m.append(create_menu(_('_Bundle rev:tip...'), self.bundle_rev_to_tip)) - m.append_sep() - m.append(create_menu(_('Add/Remove _Tag...'), self.add_tag)) - cmenu_backout = create_menu(_('Backout Revision...'), self.backout_rev) + cmenu_backout = create_menu(_('Backout...'), self.backout_rev)   m.append(cmenu_backout)   m.append(create_menu(_('_Revert'), self.revert)) - m.append(create_menu(_('_Archive...'), self.archive)) + m.append_sep() + m.append(create_submenu(_('Export'), + self.export_context_menu())) + m.append_sep() + m.append(create_submenu(_('Tag'), + self.tags_context_menu())) + m.append_sep()     # disable/enable menus as required   parents = self.repo.parents() @@ -759,39 +843,74 @@
  cmenu_merge.set_sensitive(can_merge)   cmenu_backout.set_sensitive(can_backout)   + # need mq extension for strip command + if 'mq' in self.exs: + m.append(create_submenu(_('Mercurial Queues'), + self.mq_context_menu())) +   # need transplant extension for transplant command   if 'transplant' in self.exs:   m.append(create_menu(_('Transp_lant to local'),   self.transplant_rev))   - # need mq extension for strip command - if 'mq' in self.exs: - cmenu_qimport = create_menu(_('QImport Revision'), self.qimport_rev) - cmenu_strip = create_menu(_('Strip Revision...'), self.strip_rev) - - try: - ctx = self.repo[self.currevid] - qbase = self.repo['qbase'] - actx = ctx.ancestor(qbase) - if self.repo['qparent'] == ctx: - cmenu_qimport.set_sensitive(True) - cmenu_strip.set_sensitive(False) - elif actx == qbase or actx == ctx: - # we're in the mq revision range or the mq - # is a descendant of us - cmenu_qimport.set_sensitive(False) - cmenu_strip.set_sensitive(False) - except: - pass - - m.append_sep() - m.append(cmenu_qimport) - m.append(cmenu_strip) - + m.append_sep() + m.append(create_submenu(_('Bisect'), + self.bisect_context_menu()))   menu = m.create_menu()   menu.show_all()   return menu   + def export_context_menu(self): + m = gtklib.MenuItems() + m.append(create_menu(_('_Export Patch...'), self.export_patch)) + m.append(create_menu(_('E_mail Patch...'), self.email_patch)) + m.append(create_menu(_('_Bundle rev:tip...'), self.bundle_rev_to_tip)) + m.append(create_menu(_('_Archive...'), self.archive)) + return m.create_menu() + + def tags_context_menu(self): + m = gtklib.MenuItems() + m.append(create_menu(_('Add/Remove _Tag...'), self.add_tag)) + if 'bookmarks' in self.exs: + m.append(create_menu(_('Add/Remove B_ookmark...'), + self.add_bookmark)) + m.append(create_menu(_('Rename Bookmark...'), + self.rename_bookmark)) + return m.create_menu() + + def mq_context_menu(self): + m = gtklib.MenuItems() + cmenu_qimport = create_menu(_('QImport Revision'), self.qimport_rev) + cmenu_strip = create_menu(_('Strip Revision...'), self.strip_rev) + + try: + ctx = self.repo[self.currevid] + qbase = self.repo['qbase'] + actx = ctx.ancestor(qbase) + if self.repo['qparent'] == ctx: + cmenu_qimport.set_sensitive(True) + cmenu_strip.set_sensitive(False) + elif actx == qbase or actx == ctx: + # we're in the mq revision range or the mq + # is a descendant of us + cmenu_qimport.set_sensitive(False) + cmenu_strip.set_sensitive(False) + except: + pass + + m.append_sep() + m.append(cmenu_qimport) + m.append(cmenu_strip) + return m.create_menu() + + def bisect_context_menu(self): + m = gtklib.MenuItems() + m.append(create_menu(_('Reset'), self.bisect_reset)) + m.append(create_menu(_('Mark as good'), self.bisect_good)) + m.append(create_menu(_('Mark as bad'), self.bisect_bad)) + m.append(create_menu(_('Skip testing'), self.bisect_skip)) + return m.create_menu() +   def restore_single_sel(self, widget, *args):   self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)   if self.origsel: @@ -843,7 +962,7 @@
    # need MQ extension for qimport command   if 'mq' in self.exs: - m.append(create_menu(_('QImport from here to selected'), + m.append(create_menu(_('qimport from here to selected'),   self.qimport_revs))     m.append_sep() @@ -937,9 +1056,14 @@
  _('Push outgoing changesets'))   email = syncbox.append_stock(gtk.STOCK_GOTO_LAST,   _('Email outgoing changesets')) + syncbox.append_widget(gtk.VSeparator()) + stop = syncbox.append_stock(gtk.STOCK_STOP, + _('Stop current transaction'))   + stop.set_sensitive(False)   apply.set_sensitive(False)   reject.set_sensitive(False) + self.stop_button = stop   self.syncbar_apply = apply   self.syncbar_reject = reject   @@ -995,89 +1119,37 @@
  reject.connect('clicked', self.reject_clicked)   conf.connect('clicked', self.conf_clicked, urlcombo)   email.connect('clicked', self.email_clicked) + stop.connect('clicked', self.stop_clicked)     # filter bar - self.filterbox = gtklib.SlimToolbar() + self.filterbox = FilterBox(self.tooltips, + self.filter_mode, + self.get_live_branches())   filterbox = self.filterbox + self.ancestrybutton = filterbox.ancestry + self.hidemerges = filterbox.hidemerges + self.branchbutton = filterbox.branches + self.lastbranchrow = None + self.branchcombo = filterbox.branchcombo + self.custombutton = filterbox.custombutton + self.filter_mode = filterbox.filter_mode + self.filtercombo = filterbox.filtercombo + self.filterentry = filterbox.entry + self.entrycombo = filterbox.entrycombo   - all = gtk.RadioButton(None, _('All')) - all.set_active(True) - all.connect('toggled', self.filter_selected, 'all') - filterbox.append_widget(all, padding=0) - - tagged = gtk.RadioButton(all, _('Tagged')) - tagged.connect('toggled', self.filter_selected, 'tagged') - filterbox.append_widget(tagged, padding=0) - - ancestry = gtk.RadioButton(all, _('Ancestry')) - ancestry.connect('toggled', self.filter_selected, 'ancestry') - filterbox.append_widget(ancestry, padding=0) - self.ancestrybutton = ancestry - - parents = gtk.RadioButton(all, _('Parents')) - parents.connect('toggled', self.filter_selected, 'parents') - filterbox.append_widget(parents, padding=0) - - heads = gtk.RadioButton(all, _('Heads')) - heads.connect('toggled', self.filter_selected, 'heads') - filterbox.append_widget(heads, padding=0) - - merges = gtk.RadioButton(all, _('Merges')) - merges.connect('toggled', self.filter_selected, 'only_merges') - filterbox.append_widget(merges, padding=0) - - hidemerges = gtk.CheckButton(_('Hide Merges')) - hidemerges.connect('toggled', self.filter_selected, 'no_merges') - filterbox.append_widget(hidemerges, padding=0) - self.hidemerges = hidemerges - - branches = gtk.RadioButton(all) - branches.connect('toggled', self.filter_selected, 'branch') - self.tooltips.set_tip(branches, _('Branch Filter')) - branches.set_sensitive(False) - filterbox.append_widget(branches, padding=0) - self.branchbutton = branches - - branchcombo = gtk.combo_box_new_text() - branchcombo.append_text(_('Branches...')) - for name in self.get_live_branches(): - branchcombo.append_text(hglib.toutf(name)) - branchcombo.set_active(0) - branchcombo.connect('changed', self.select_branch) - self.lastbranchrow = None - filterbox.append_widget(branchcombo, padding=0) - self.branchcombo = branchcombo - - self.custombutton = gtk.RadioButton(all) - self.tooltips.set_tip(self.custombutton, _('Custom Filter')) - self.custombutton.set_sensitive(False) - filterbox.append_widget(self.custombutton, padding=0) - - filtercombo = gtk.combo_box_new_text() - filtercombo_entries = (_('Rev Range'), _('File Patterns'), - _('Keywords'), _('Date'), _('User')) - for f in filtercombo_entries: - filtercombo.append_text(f) - if (self.filter_mode >= len(filtercombo_entries) or - self.filter_mode < 0): - self.filter_mode = 1 - filtercombo.set_active(self.filter_mode) - self.filtercombo = filtercombo - filterbox.append_widget(filtercombo, padding=0) - - searchlist = gtk.ListStore(int, # filtercombo value - str, # search string (utf-8) - str) # mode string (utf-8) - entrycombo = gtk.ComboBoxEntry(searchlist, 1) - cell = gtk.CellRendererText() - entrycombo.pack_end(cell, False) - entrycombo.add_attribute(cell, 'text', 2) - entry = entrycombo.child - entry.connect('activate', self.filter_entry_activated, filtercombo) - entrycombo.connect('changed', self.filter_entry_changed, filtercombo) - self.entrycombo = entrycombo - self.filterentry = entry - filterbox.append_widget(entrycombo, expand=True, padding=0) + fcon = self.filterbox.connect + fsel = self.filter_selected + fcon('all_toggled', fsel, 'all') + fcon('tagged_toggled', fsel, 'tagged') + fcon('ancestry_toggled', fsel, 'ancestry') + fcon('parents_toggled', fsel, 'parents') + fcon('heads_toggled', fsel, 'heads') + fcon('merges_toggled', fsel, 'only_merges') + fcon('hidemerges_toggled', fsel, 'no_merges') + fcon('branches_toggled', fsel, 'branch') + fcon('branchcombo_changed', self.select_branch) + fcon('entry_activate', self.filter_entry_activated, self.filtercombo) + fcon('entrycombo_changed', self.filter_entry_changed, self.filtercombo)     midpane = gtk.VBox()   midpane.pack_start(syncbox, False) @@ -1134,6 +1206,15 @@
  def get_extras(self):   return self.stbar   + def refresh_on_marker_change(self, oldlen, oldmarkers, newmarkers): + self.repo.invalidate() + self.changeview.clear_cache() + if len(self.repo) != oldlen: + self.reload_log() + else: + if newmarkers != oldmarkers: + self.refresh_model() +   def apply_clicked(self, button):   combo = self.ppullcombo   list, iter = combo.get_model(), combo.get_active_iter() @@ -1209,22 +1290,35 @@
  atexit.register(cleanup)     bfile = path - path = hglib.validate_synch_path(path, self.repo) - -   for badchar in (':', '*', '\\', '?', '#'):   bfile = bfile.replace(badchar, '')   bfile = bfile.replace('/', '_')   bfile = os.path.join(self.bundledir, bfile) + '.hg'   cmdline = ['hg', 'incoming', '--bundle', bfile]   cmdline += self.get_proxy_args() - cmdline += [path] - dlg = hgcmd.CmdDialog(cmdline, text='hg incoming') - dlg.show_all() - dlg.run() - dlg.hide() - if dlg.return_code() == 0 and os.path.isfile(bfile): - self.set_bundlefile(bfile) + cmdline += [hglib.validate_synch_path(path, self.repo)] + + def callback(return_code, *args): + self.stbar.end() + self.stop_button.set_sensitive(False) + self.cmd_set_sensitive('stop', False) + if return_code == 0 and os.path.isfile(bfile): + self.set_bundlefile(bfile) + text = _('%d incoming changesets') % self.npreviews + elif return_code is None: + text = _('Aborted incoming') + else: + text = _('No incoming changesets') + self.stbar.set_idle_text(text) + if self.runner.execute(cmdline, callback): + self.runner.set_title(_('Incoming')) + self.stbar.begin(_('Checking incoming changesets...')) + self.stop_button.set_sensitive(True) + self.cmd_set_sensitive('stop', True) + else: + gdialog.Prompt(_('Cannot run now'), + _('Please try again after running ' + 'operation is completed'), self).run()     def set_bundlefile(self, bfile, **kwopts):   self.origurl = self.urlcombo.get_active() @@ -1254,14 +1348,15 @@
  self.cmd_set_sensitive('accept', True)   self.cmd_set_sensitive('reject', True)   - cmds = ('incoming', 'outgoing', 'push', 'pull', 'email', 'refresh', - 'synchronize', 'mq', 'add-bundle') + cmds = ('incoming', 'outgoing', 'push', 'pull', 'email', 'refresh', + 'synchronize', 'mq', 'add-bundle')   self.incoming_disabled_cmds = []   for cmd in cmds:   self.cmd_set_sensitive(cmd, False)   self.incoming_disabled_cmds.append(cmd)   - ignore = (self.syncbar_apply, self.syncbar_reject, self.ppullbox) + ignore = (self.syncbar_apply, self.syncbar_reject, self.ppullbox, + self.stop_button)   self.incoming_disabled = []   def disable_child(w):   if (w not in ignore) and w.get_property('sensitive'): @@ -1318,18 +1413,35 @@
  self.pathentry.grab_focus()   return   cmdline = ['hg'] + cmd + self.get_proxy_args() + [remote_path] - dlg = hgcmd.CmdDialog(cmdline, text=' '.join(['hg'] + cmd)) - dlg.show_all() - dlg.run() - dlg.hide() - if dlg.return_code() == 0: - self.repo.invalidate() - self.changeview.clear_cache() - if '--rebase' in cmd: - self.origtip = len(self.repo) - self.reload_log() - elif len(self.repo) > self.origtip: - self.reload_log() + + def callback(return_code, *args): + self.stbar.end() + self.stop_button.set_sensitive(False) + self.cmd_set_sensitive('stop', False) + if return_code == 0: + self.repo.invalidate() + self.changeview.clear_cache() + if '--rebase' in cmd: + self.origtip = len(self.repo) + self.reload_log() + text = _('Finished pull with rebase') + elif len(self.repo) > self.origtip: + self.reload_log() + text = _('Finished pull') + else: + text = _('No changesets to pull') + else: + text = _('Aborted pull') + self.stbar.set_idle_text(text) + if self.runner.execute(cmdline, callback): + self.runner.set_title(_('Pull')) + self.stbar.begin(_('Pulling changesets...')) + self.stop_button.set_sensitive(True) + self.cmd_set_sensitive('stop', True) + else: + gdialog.Prompt(_('Cannot run now'), + _('Please try again after running ' + 'operation is completed'), self).run()     def outgoing_clicked(self, toolbutton):   path = hglib.fromutf(self.pathentry.get_text()).strip() @@ -1343,24 +1455,35 @@
  cmd += self.get_proxy_args()   cmd += [hglib.validate_synch_path(path, self.repo)]   - dlg = hgcmd.CmdDialog(cmd, text='hg outgoing') - dlg.show_all() - dlg.run() - dlg.hide() - if dlg.return_code() == 0: - outgoing = [] - buf = dlg.textbuffer - begin, end = buf.get_bounds() - for line in buf.get_text(begin, end).splitlines()[:-1]: - try: - node = self.repo[line].node() - outgoing.append(node) - except: - pass - self.outgoing = outgoing - self.reload_log() - text = _('%d outgoing changesets') % len(outgoing) + def callback(return_code, buffer, *args): + self.stbar.end() + self.stop_button.set_sensitive(False) + self.cmd_set_sensitive('stop', False) + if return_code == 0: + outgoing = [] + for line in buffer.splitlines()[:-1]: + try: + node = self.repo[line].node() + outgoing.append(node) + except: + pass + self.outgoing = outgoing + self.reload_log() + text = _('%d outgoing changesets') % len(outgoing) + elif return_code is None: + text = _('Aborted outgoing') + else: + text = _('No outgoing changesets')   self.stbar.set_idle_text(text) + if self.runner.execute(cmd, callback): + self.runner.set_title(_('Outgoing')) + self.stbar.begin(_('Checking outgoing changesets...')) + self.stop_button.set_sensitive(True) + self.cmd_set_sensitive('stop', True) + else: + gdialog.Prompt(_('Cannot run now'), + _('Please try again after running ' + 'operation is completed'), self).run()     def email_clicked(self, toolbutton):   path = hglib.fromutf(self.pathentry.get_text()).strip() @@ -1412,13 +1535,28 @@
  if self.forcepush:   cmdline += ['--force']   cmdline += [remote_path] - dlg = hgcmd.CmdDialog(cmdline, text=' '.join(cmdline[:-1])) - dlg.show_all() - dlg.run() - dlg.hide() - if dlg.return_code() == 0 and self.outgoing: - self.outgoing = [] - self.reload_log() + + def callback(return_code, *args): + self.stbar.end() + self.stop_button.set_sensitive(False) + self.cmd_set_sensitive('stop', False) + if return_code == 0: + if self.outgoing: + self.outgoing = [] + self.reload_log() + text = _('Finished push') + else: + text = _('Aborted push') + self.stbar.set_idle_text(text) + if self.runner.execute(cmdline, callback): + self.runner.set_title(_('Push')) + self.stbar.begin(_('Pushing changesets...')) + self.stop_button.set_sensitive(True) + self.cmd_set_sensitive('stop', True) + else: + gdialog.Prompt(_('Cannot run now'), + _('Please try again after running ' + 'operation is completed'), self).run()     def conf_clicked(self, toolbutton, combo):   newpath = hglib.fromutf(self.pathentry.get_text()).strip() @@ -1439,6 +1577,9 @@
  self.update_urllist()   self.update_postpull()   + def stop_clicked(self, toolbutton): + self.runner.stop() +   def update_urllist(self):   urllist = self.urlcombo.get_model()   urllist.clear() @@ -1723,19 +1864,74 @@
  tag = ''     def refresh(*args): - self.repo.invalidate() - self.changeview.clear_cache() - if len(self.repo) != oldlen: - self.reload_log() - else: - newtags = self.repo.tagslist() - if newtags != oldtags: - self.refresh_model() + self.refresh_on_marker_change(oldlen, oldtags, self.repo.tagslist())     dialog = tagadd.TagAddDialog(self.repo, tag, rev)   dialog.connect('destroy', refresh)   self.show_dialog(dialog)   + def add_bookmark(self, menuitem): + # save bookmark info for detecting new bookmarks added + oldbookmarks = hglib.get_repo_bookmarks(self.repo) + oldlen = len(self.repo) + rev = self.currevid + + def refresh(*args): + self.refresh_on_marker_change(oldlen, + oldbookmarks, + hglib.get_repo_bookmarks(self.repo)) + + dialog = bookmark.BookmarkAddDialog(self.repo, rev=str(rev)) + dialog.connect('destroy', refresh) + self.show_dialog(dialog) + + def rename_bookmark(self, menuitem): + # save bookmark info for detecting new bookmarks added + oldbookmarks = hglib.get_repo_bookmarks(self.repo) + oldlen = len(self.repo) + rev = self.currevid + + def refresh(*args): + self.refresh_on_marker_change(oldlen, + oldbookmarks, + hglib.get_repo_bookmarks(self.repo)) + + dialog = bookmark.BookmarkRenameDialog(self.repo, rev=str(rev)) + dialog.connect('destroy', refresh) + self.show_dialog(dialog) + + def bisect_reset(self, menuitem): + commands.bisect(ui=self.ui, + repo=self.repo, + good=False, + bad=False, + skip=False, + reset=True) + + def bisect_good(self, menuitem): + cmd = ['hg', 'bisect', '--good', str(self.currevid)] + dlg = hgcmd.CmdDialog(cmd) + dlg.show_all() + dlg.run() + dlg.hide() + self.refresh_model() + + def bisect_bad(self, menuitem): + cmd = ['hg', 'bisect', '--bad', str(self.currevid)] + dlg = hgcmd.CmdDialog(cmd) + dlg.show_all() + dlg.run() + dlg.hide() + self.refresh_model() + + def bisect_skip(self, menuitem): + cmd = ['hg', 'bisect', '--skip', str(self.currevid)] + dlg = hgcmd.CmdDialog(cmd) + dlg.show_all() + dlg.run() + dlg.hide() + self.refresh_model() +   def show_status(self, menuitem):   rev = self.currevid   statopts = self.merge_opts(commands.table['^status|st'][1], @@ -1766,33 +1962,65 @@
  self.pathentry.grab_focus()   return   node = self.repo[self.currevid].node() - cmdline = ['hg', 'push', '--rev', str(self.currevid), remote_path] - dlg = hgcmd.CmdDialog(cmdline, text='hg push') - dlg.show_all() - dlg.run() - dlg.hide() - if dlg.return_code() == 0 and self.outgoing: - d = self.outgoing.index(node) - self.outgoing = self.outgoing[d+1:] - self.reload_log() + rev = str(self.currevid) + cmdline = ['hg', 'push', '--rev', rev, remote_path] + + def callback(return_code, *args): + self.stbar.end() + self.stop_button.set_sensitive(False) + self.cmd_set_sensitive('stop', False) + if return_code == 0: + if self.outgoing: + d = self.outgoing.index(node) + self.outgoing = self.outgoing[d + 1:] + self.reload_log() + text = _('Finished push to revision %s') % rev + else: + text = _('Aborted push') + self.stbar.set_idle_text(text) + if self.runner.execute(cmdline, callback): + self.runner.set_title(_('Push to %s') % rev) + self.stbar.begin(_('Pushing changesets to revision %s...') % rev) + self.stop_button.set_sensitive(True) + self.cmd_set_sensitive('stop', True) + else: + gdialog.Prompt(_('Cannot run now'), + _('Please try again after running ' + 'operation is completed'), self).run()     def pull_to(self, menuitem): - cmdline = ['hg', 'pull', '--rev', str(self.currevid), self.bfile] - dlg = hgcmd.CmdDialog(cmdline) - dlg.show_all() - dlg.run() - dlg.hide() - curtip = len(hg.repository(self.ui, self.repo.root)) - self.repo = hg.repository(self.ui, path=self.bfile) - self.graphview.set_repo(self.repo, self.stbar) - self.changeview.set_repo(self.repo) - if hasattr(self, 'mqwidget'): - self.mqwidget.set_repo(self.repo) - self.npreviews = len(self.repo) - curtip - if self.npreviews == 0: - self.remove_overlay(False) + rev = str(self.currevid) + cmdline = ['hg', 'pull', '--rev', rev, self.bfile] + + def callback(return_code, *args): + self.stbar.end() + self.stop_button.set_sensitive(False) + self.cmd_set_sensitive('stop', False) + if return_code == 0: + curtip = len(hg.repository(self.ui, self.repo.root)) + self.repo = hg.repository(self.ui, path=self.bfile) + self.graphview.set_repo(self.repo, self.stbar) + self.changeview.set_repo(self.repo) + if hasattr(self, 'mqwidget'): + self.mqwidget.set_repo(self.repo) + self.npreviews = len(self.repo) - curtip + if self.npreviews == 0: + self.remove_overlay(False) + else: + self.reload_log() + text = _('Finished pull to revision %s') % rev + else: + text = _('Aborted pull') + self.stbar.set_idle_text(text) + if self.runner.execute(cmdline, callback): + self.runner.set_title(_('Pull to %s') % rev) + self.stbar.begin(_('Pulling changesets to revision %s...') % rev) + self.stop_button.set_sensitive(True) + self.cmd_set_sensitive('stop', True)   else: - self.reload_log() + gdialog.Prompt(_('Cannot run now'), + _('Please try again after running ' + 'operation is completed'), self).run()     def copy_hash(self, menuitem):   hash = self.repo[self.currevid].hex()
(No changes)
(No changes)
 
159
160
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
163
164
165
166
167
168
 
 
 
 
169
170
171
 
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
 
 
181
182
183
184
185
186
187
@@ -159,13 +159,29 @@
  escape = gtklib.markup_escape_text   summary = escape(hglib.toutf(summary))   node = ctx.node() + + # Check if we're using bookmarks, and have the + # 'track.current' option set; if so, + # find what the 'current' bookmark is + currentBookmark = None + bookmarks = None + try: + bookmarks = hglib.extensions.find('bookmarks') + except KeyError: + pass + if bookmarks: + if self.repo.ui.configbool('bookmarks', 'track.current'): + currentBookmark = bookmarks.current(self.repo) +   tags = self.repo.nodetags(node)   taglist = hglib.toutf(', '.join(tags))   tstr = ''   for tag in tags:   if tag not in self.hidetags: - tstr += gtklib.markup(' %s ' % tag, color='black', - background='#ffffaa') + ' ' + style = {'color':'black', 'background':'#ffffaa'} + if tag == currentBookmark: + style['background'] = '#ffcc99' + tstr += gtklib.markup(' %s ' % tag, **style) + ' '     branch = ctx.branch()   bstr = ''
 
392
393
394
 
395
396
397
 
392
393
394
395
396
397
398
@@ -392,6 +392,7 @@
  cell = gtk.CellRendererText()   cell.set_property("width-chars", 8)   cell.set_property("ellipsize", pango.ELLIPSIZE_END) + cell.set_property("xalign", 1.0)   col = self.tvcolumns['rev'] = gtk.TreeViewColumn(_('Rev'))   col.set_visible(False)   col.set_resizable(True)
Show Entire File tortoisehg/​hgtk/​quickop.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
 
26
27
28
29
 
30
31
32
 
26
27
28
 
29
30
31
32
@@ -26,7 +26,7 @@
  fname = util.canonpath(root, cwd, pats[0])   target = util.canonpath(root, cwd, pats[1])   except util.Abort, e: - return gdialog.Prompt("invalid path", str(e), None) + return gdialog.Prompt(_('Invalid path'), str(e), None)   except IndexError:   pass   os.chdir(root)
 
42
43
44
45
 
46
47
48
 
42
43
44
 
45
46
47
48
@@ -42,7 +42,7 @@
  self.pbar.pulse()   return True   - def begin(self, msg=_('Running'), timeout=100): + def begin(self, msg=_('Running...'), timeout=100):   self.pbox.set_child_visible(True)   self.pbox.map()   self.set_status_text(msg)
 
559
560
561
 
 
 
 
 
 
562
563
564
 
570
571
572
573
 
 
 
 
574
575
576
577
578
 
579
580
581
 
1064
1065
1066
 
 
 
 
 
 
 
 
1067
1068
1069
 
559
560
561
562
563
564
565
566
567
568
569
570
 
576
577
578
 
579
580
581
582
583
584
585
586
 
587
588
589
590
 
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
@@ -559,6 +559,12 @@
  self.connect('thg-accept', self.thgaccept)   self.connect('delete-event', self.delete_event)   + # wrapper box for padding + wrapbox = gtk.VBox() + wrapbox.set_border_width(5) + self.vbox.pack_start(wrapbox, False, False) + + # create combo to select the target   combo = gtk.combo_box_new_text()   combo.append_text(_('User global settings'))   if repo: @@ -570,12 +576,15 @@
  edit = gtk.Button(_('Edit File'))   hbox.pack_start(edit, False, False, 2)   edit.connect('clicked', self.edit_clicked) - self.vbox.pack_start(hbox, False, False, 4) + wrapbox.pack_start(hbox, False, False, 1) + + # insert padding of between combo and notebook + wrapbox.pack_start(gtk.VBox(), False, False, 3)     # Create a new notebook, place the position of the tabs   self.notebook = notebook = gtk.Notebook()   notebook.set_tab_pos(gtk.POS_TOP) - self.vbox.pack_start(notebook, True, True) + wrapbox.pack_start(notebook, True, True)   notebook.show()   self.show_tabs = True   self.show_border = True @@ -1064,6 +1073,14 @@
  break   except (IOError, OSError):   pass + else: + gdialog.Prompt(_('Unable to create a Mercurial.ini file'), + _('Insufficient access rights, reverting to read-only' + 'mode.'), self).run() + from mercurial import config + self.fn = rcpath[0] + cfg = config.config() + return cfg   self.fn = fn   try:   import iniparse
Show Entire File tortoisehg/​hgtk/​thgmq.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
 
236
237
238
 
 
 
236
237
238
239
240
@@ -236,3 +236,5 @@
  return_path = path_aux   return return_path   +def get_repo_bookmarks(repo): + return repo._bookmarks.keys()
Show Entire File tortoisehg/​util/​hgversion.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​util/​shlib.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File tortoisehg/​util/​thread2.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
(No changes)
Show Entire File win32/​shellext/​Makefile Stacked
This file's diff was not loaded because this changeset is very large. Load changes
Show Entire File win32/​shellext/​Makefile.nmake Stacked
(No changes)