Kiln » TortoiseHg » TortoiseHg
Clone URL:  
Pushed to one repository · View In Graph Contained in 0.8, 0.8.1, and 0.8.2

merge with Simon Heimberg

Changeset 225fc4c38ac7

Parents 090ac0b4f8ab

Parents 5a2a48e63e43

by Steve Borho

Changes to 15 files · Browse files at 225fc4c38ac7 Showing diff from parent 090ac0b4f8ab 5a2a48e63e43 Diff from another changeset...

 
1
 
2
3
 
4
5
6
 
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
 
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
 
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
261
262
263
264
265
266
267
268
269
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
 
 
 
 
325
326
327
328
329
330
331
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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
 
 
 
431
432
433
 
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
 
 
 
 
 
 
 
 
 
467
468
 
 
 
 
 
469
470
471
472
473
474
475
476
477
478
479
 
 
 
 
 
 
 
 
 
480
481
482
 
 
 
483
484
485
 
486
487
488
489
 
490
491
492
493
494
495
496
 
497
498
499
 
505
506
507
508
509
510
 
511
512
513
 
528
529
530
531
532
533
534
 
 
 
 
535
536
537
538
539
540
 
 
 
 
 
541
542
 
543
544
 
545
546
547
 
548
549
 
 
 
1
2
 
3
4
5
6
 
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
 
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
 
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
 
254
255
256
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
258
259
260
261
262
263
264
265
266
267
268
269
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
297
298
299
 
300
301
302
303
 
309
310
311
 
312
 
313
314
315
316
 
331
332
333
 
 
 
 
334
335
336
337
338
 
 
 
 
 
339
340
341
342
343
344
 
345
346
 
347
348
 
 
349
350
 
351
@@ -1,6 +1,6 @@
-# Trivial Mercurial plugin for Nautilus +# TortoiseHg plugin for Nautilus  # -# Copyright (C) 2007 Steve Borho +# Copyright (C) 2007-9 Steve Borho  #  # Stolen mercilessly from nautilus-bzr, thanks guys  # Copyright (C) 2006 Jeff Bailey @@ -9,26 +9,36 @@
 #  # Published under the GNU GPL   -import gconf  import gtk  import gobject -from mercurial import hg, ui, match, util -from mercurial.node import short  import nautilus + +try: + from mercurial import demandimport +except ImportError: + # workaround to use user's local python libs + userlib = os.path.expanduser('~/lib/python') + if os.path.exists(userlib) and userlib not in sys.path: + sys.path.append(userlib) + from mercurial import demandimport +demandimport.enable() +  import os  import subprocess  import sys -import tempfile -import time  import urllib   +from mercurial import hg, ui, match, util  try:   from mercurial.error import RepoError  except ImportError:   from mercurial.repo import RepoError +from mercurial.node import short    TORTOISEHG_PATH = '~/tools/tortoisehg-dev' -TERMINAL_KEY = '/desktop/gnome/applications/terminal/exec' +nofilecmds = 'about serve synch repoconfig userconfig merge unmerge'.split() +nocachecmds = 'about serve repoconfig userconfig'.split() +    class HgExtension(nautilus.MenuProvider,   nautilus.ColumnProvider, @@ -38,26 +48,55 @@
  def __init__(self):   self.cacherepo = None   self.cacheroot = None - self.client = gconf.client_get_default() - thgpath = os.environ.get('TORTOISEHG_PATH', - os.path.expanduser(TORTOISEHG_PATH)) - os.environ['TORTOISEHG_PATH'] = thgpath - os.environ['THG_ICON_PATH'] = os.path.join(thgpath, 'icons') - self.hgproc = os.path.join(thgpath, 'hgproc.py') + self.scanStack = [] + + # check if nautilus-thg.py is a symlink first + pfile = __file__ + if pfile.endswith('.pyc'): + pfile = pfile[:-1] + path = os.path.dirname(os.path.realpath(pfile)) + thgpath = os.path.normpath(os.path.join(path, '..')) + testpath = os.path.join(thgpath, 'tortoise') + if os.path.isdir(testpath): + if thgpath not in sys.path: + sys.path.insert(0, thgpath) + else: + # try environment or hard-coded path + thgpath = os.environ.get('TORTOISEHG_PATH', TORTOISEHG_PATH) + thgpath = os.path.normpath(os.path.expanduser(thgpath)) + if os.path.exists(thgpath) and thgpath not in sys.path: + sys.path.insert(0, thgpath) + # else assume tortoise is already in PYTHONPATH + try: + import tortoise.thgutil + import tortoise.menuthg + except ImportError, e: + # if thgutil is not found, then repository cannot be found + # if menuthg is not found, you have an older version in sys.path + print e + self.menu = None + return + + self.env = os.environ + self.env['PYTHONPATH'] = ':'.join(sys.path) + self.env['TORTOISEHG_PATH'] = thgpath + self.env['THG_ICON_PATH'] = os.path.join(thgpath, 'icons') + + self.hgproc = tortoise.thgutil.find_path('hgtk', + tortoise.thgutil.get_prog_root()) + if not self.hgproc: + self.hgproc = tortoise.thgutil.find_path('hgtk')   self.ipath = os.path.join(thgpath, 'icons', 'tortoise') + self.menu = tortoise.menuthg.menuThg()     def icon(self, iname):   return os.path.join(self.ipath, iname)     def get_path_for_vfs_file(self, vfs_file): - if vfs_file.get_uri_scheme() != 'file': + if vfs_file.is_gone() or vfs_file.get_uri_scheme() != 'file':   return None   return urllib.unquote(vfs_file.get_uri()[7:])   - def clear_cached_repo(self): - self.cacheroot = None - self.cacherepo = None -   def get_repo_for_path(self, path):   '''   Find mercurial repository for vfs_file @@ -81,353 +120,132 @@
  self.cacheroot = None   self.cacherepo = None   return None + except StandardError, e: + print e + return None   - def _open_terminal_cb(self, window, vfs_file): - path = self.get_path_for_vfs_file(vfs_file) - if path is None: - return - os.chdir(path) - terminal = self.client.get_string(TERMINAL_KEY) - os.system('%s &' % terminal) - - def _about_cb(self, window, vfs_file): - self._run_dialog('about', [vfs_file]) - - def _add_cb(self, window, vfs_files): - self._run_dialog('add', vfs_files) - self.clear_cached_repo() - - def _clone_cb(self, window, vfs_file): - self._run_dialog('clone', [vfs_file]) - - def _commit_cb(self, window, vfs_files): - self._run_dialog('commit', vfs_files) - self.clear_cached_repo() - - def _datamine_cb(self, window, vfs_files): - self._run_dialog('datamine', vfs_files) - - def _diff_cb(self, window, vfs_files): - path = self.get_path_for_vfs_file(vfs_files[0]) - if path is None: - return - repo = self.get_repo_for_path(path) - if repo is None: - return - diffcmd = repo.ui.config('tortoisehg', 'vdiff', None) - if diffcmd is None: - self._run_dialog('diff', vfs_files) - else: - cmdline = ['hg', diffcmd] - cwd = os.path.isdir(path) and path or os.path.dirname(path) - paths = [self.get_path_for_vfs_file(f) for f in vfs_files] - subprocess.Popen(cmdline + paths, shell=False, cwd=cwd) - - def _history_cb(self, window, vfs_files): - self._run_dialog('history', vfs_files) - self.clear_cached_repo() - - def _init_cb(self, window, vfs_file): - self._run_dialog('init', [vfs_file]) - - def _recovery_cb(self, window, vfs_file): - self._run_dialog('recovery', [vfs_file]) - self.clear_cached_repo() - - def _revert_cb(self, window, vfs_files): - self._run_dialog('revert', vfs_files) - self.clear_cached_repo() - - def _serve_cb(self, window, vfs_file): - self._run_dialog('serve', [vfs_file], filelist=False) - - def _status_cb(self, window, vfs_file): - self._run_dialog('status', [vfs_file]) - - def _sync_cb(self, window, vfs_file): - self._run_dialog('synch', [vfs_file], filelist=False) - self.clear_cached_repo() - - def _thgconfig_repo_cb(self, window, vfs_file): - self._run_dialog('config', [vfs_file]) - - def _thgconfig_user_cb(self, window, vfs_file): - self._run_dialog('config', [vfs_file], filelist=False) - - def _unmerge_cb(self, window, vfs_file): - self._run_dialog('checkout', [vfs_file], filelist=False, - extras=['--', '--clean', str(self.rev0)]) - self.clear_cached_repo() - - def _run_dialog(self, hgcmd, vfs_files, filelist=True, extras=[]): + def run_dialog(self, menuitem, hgcmd, cwd = None):   '''   hgcmd - hgproc subcommand - vfs_files - directory, or list of selected files - filelist - bool for whether to generate file list for hgproc   ''' - paths = [self.get_path_for_vfs_file(f) for f in vfs_files] - if paths[0] is None: + if cwd: #bg + self.files = [] + else: + cwd = self.cwd + repo = self.get_repo_for_path(cwd) + + if hgcmd == 'vdiff': + diffcmd = repo.ui.config('tortoisehg', 'vdiff', None) + if diffcmd is None: + hgcmd = 'diff' + else: + cmdline = ['hg', diffcmd] + cmdline.extend(self.files) + subprocess.Popen(cmdline, shell=False, env=self.env, cwd=cwd) + return + + cmdopts = [sys.executable, self.hgproc, hgcmd] + + if hgcmd not in nofilecmds and self.files: + # Use stdin to pass file list (avoid shell command + # line limitations) + pipe = subprocess.PIPE + cmdopts += ['--listfile', '-'] + else: + pipe = None + + stdin = subprocess.Popen(cmdopts, cwd=cwd, stdin=pipe, env=self.env, shell=False).stdin + + if pipe: + stdin.write('\n'.join(self.files)) + stdin.close() + + if hgcmd not in nocachecmds: + # Remove cached repo object, dirstate may change + self.cacherepo = None + self.cacheroot = None + + def buildMenu(self, vfs_files, bg): + '''Build menu''' + self.pos = 0 + self.files = [] + files = [] + for vfs_file in vfs_files: + f = self.get_path_for_vfs_file(vfs_file) + if f: + files.append(f) + if not files:   return + if bg or len(files) == 1 and vfs_files[0].is_directory(): + cwd = files[0] + files = [] + else: + cwd = os.path.dirname(files[0]) + repo = self.get_repo_for_path(cwd) + if repo: + menus = self.menu.get_commands(repo, cwd, files) + if cwd == repo.root: + cwd_rel = '' + else: + cwd_rel = cwd[len(repo.root+os.sep):] + os.sep + for f in files: + cpath = util.canonpath(repo.root, cwd, f) + if cpath.startswith(cwd_rel): + cpath = cpath[len(cwd_rel):] + self.files.append(cpath) + else: + self.files.append(f) + else: + menus = self.menu.get_norepo_commands(cwd, files) + self.cwd = cwd + return self._buildMenu(menus)   - path = paths[0] - repo = self.get_repo_for_path(path) - cwd = os.path.isdir(path) and path or os.path.dirname(path) - - if repo is not None: - root = repo.root - else: - root = cwd - - cmdopts = [sys.executable, self.hgproc] - cmdopts += ['--root', root] - cmdopts += ['--cwd', cwd] - cmdopts += ['--command', hgcmd] - - if filelist: - # Use temporary file to store file list (avoid shell command - # line limitations) - fd, tmpfile = tempfile.mkstemp(prefix="tortoisehg_filelist_") - os.write(fd, "\n".join(paths)) - os.close(fd) - cmdopts += ['--listfile', tmpfile, '--deletelistfile'] - cmdopts.extend(extras) - - subprocess.Popen(cmdopts, cwd=cwd, shell=False) - - # Remove cached repo object, dirstate may change - self.cacherepo = None - self.cacheroot = None + def _buildMenu(self, menus): + '''Build one level of a menu''' + items = [] + if self.files: + passcwd = None + else: #bg + passcwd = self.cwd + for menu_info in menus: + idstr = 'HgNautilus::%02d' % self.pos + self.pos += 1 + if menu_info.isSep(): + # can not insert a separator till now + pass + elif menu_info.isSubmenu(): + if hasattr(nautilus, 'Menu'): + item = nautilus.MenuItem(idstr, menu_info.menutext, + menu_info.helptext) + submenu = nautilus.Menu() + item.set_submenu(submenu) + for subitem in self._buildMenu(menu_info.get_menus()): + submenu.append_item(subitem) + items.append(item) + else: #submenu not suported + for subitem in self._buildMenu(menu_info.get_menus()): + items.append(subitem) + else: + if menu_info.state: + item = nautilus.MenuItem(idstr, + menu_info.menutext, + menu_info.helptext, + self.icon(menu_info.icon)) + item.connect('activate', self.run_dialog, menu_info.hgcmd, passcwd) + items.append(item) + return items     def get_background_items(self, window, vfs_file):   '''Build context menu for current directory''' - mainitem = nautilus.MenuItem('HgNautilus', 'Mercurial', '') - submenu = nautilus.Menu() - mainitem.set_submenu(submenu) - - path = self.get_path_for_vfs_file(vfs_file) - if path is None: - return - - repo = self.get_repo_for_path(path) - if repo is None: - ''' The name given to nautilus.MenuItem decides the - of the menu, which is ordered alpahbetically ''' - item = nautilus.MenuItem('HgNautilus::newtree', - 'Create New Repository', - 'Make directory versioned', - self.icon('menucreaterepos.ico')) - item.connect('activate', self._init_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::clone', - 'Create Clone', - 'Create clone here from source', - self.icon('menuclone.ico')) - item.connect('activate', self._clone_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::99about', - 'About TortoiseHg', - 'Information about TortoiseHg installation', - self.icon('menuabout.ico')) - item.connect('activate', self._about_cb, vfs_file) - submenu.append_item(item) - - return mainitem, - - if len(repo.changectx(None).parents()) > 1: - self.rev0 = repo.changectx(None).parents()[0].rev() - item = nautilus.MenuItem('HgNautilus::undomerge', - 'Undo Merge', - 'Clean checkout of original parent revision', - self.icon('menuunmerge.ico')) - item.connect('activate', self._unmerge_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::10commit', - 'Commit', - 'Commit changes', - self.icon('menucommit.ico')) - item.connect('activate', self._commit_cb, [vfs_file]) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::20status', - 'Show Status', - 'Show Repository Status', - self.icon('menushowchanged.ico')) - item.connect('activate', self._status_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::30diff', - 'Visual Diff', - 'Show Changes to Repository', - self.icon('menudiff.ico')) - item.connect('activate', self._diff_cb, [vfs_file]) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::40dag', - 'Revision History', - 'Show revision DAG', - self.icon('menurevisiongraph.ico')) - item.connect('activate', self._history_cb, [vfs_file]) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::50datamine', - 'Data Mining', - 'Search revision history', - self.icon('menulog.ico')) - item.connect('activate', self._datamine_cb, [vfs_file]) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::60sync', - 'Synchronize', - 'Sync with another repository', - self.icon('menusynch.ico')) - item.connect('activate', self._sync_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::70serve', - 'Web Server', - 'Start internal web server', - self.icon('proxy.ico')) - item.connect('activate', self._serve_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::75recover', - 'Recovery', - 'General repair and recovery of repository', - self.icon('general.ico')) - item.connect('activate', self._recovery_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::80repoconfig', - 'Repository Settings', - 'Configure Mercurial settings for this repo', - self.icon('menusettings.ico')) - item.connect('activate', self._thgconfig_repo_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::85userconfig', - 'User-Global Settings', - 'Configure global Mercurial settings', - self.icon('menusettings.ico')) - item.connect('activate', self._thgconfig_user_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::99about', - 'About TortoiseHg', - 'Information about TortoiseHg installation', - self.icon('menuabout.ico')) - item.connect('activate', self._about_cb, vfs_file) - submenu.append_item(item) - - return mainitem, + if vfs_file and self.menu: + return self.buildMenu([vfs_file], True) + else: + self.files = []     def get_file_items(self, window, vfs_files): - mainitem = nautilus.MenuItem('HgNautilus', 'Mercurial', '') - submenu = nautilus.Menu() - mainitem.set_submenu(submenu) - - '''Build context menu for selected files''' - if not vfs_files: - return None - - vfs_file = vfs_files[0] - path = self.get_path_for_vfs_file(vfs_file) - repo = self.get_repo_for_path(path) - if repo is None: - if not vfs_file.is_directory(): - return None - - # Menu for unrevisioned subdirectory - name = vfs_files[0].get_name() - item = nautilus.MenuItem('HgNautilus::10newtree', - 'Make directory versioned', - 'Create Repository in %s' % name, - self.icon('menucreaterepos.ico')) - item.connect('activate', self._init_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::20clone', - 'Create clone from source', - 'Create Clone in %s' % name, - self.icon('menuclone.ico')) - item.connect('activate', self._clone_cb, vfs_file) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::99about', - 'About TortoiseHg', - 'Information about TortoiseHg installation', - self.icon('menuabout.ico')) - item.connect('activate', self._about_cb, vfs_file) - submenu.append_item(item) - - return mainitem, - - localpaths = [] - for vfs_file in vfs_files: - path = self.get_path_for_vfs_file(vfs_file) - if path is None: - continue - localpath = path[len(repo.root)+1:] - localpaths.append(localpath) - - if not localpaths: - return - path = localpaths[0] - cwd = os.path.isdir(path) and path or os.path.dirname(path) - matcher = match.exact(repo.root, cwd, localpaths) - changes = repo.dirstate.status(matcher, True, True, True) - (lookup, modified, added, removed, deleted, unknown, - ignored, clean) = changes - - # Add menu items based on states list - if unknown: - item = nautilus.MenuItem('HgNautilus::30add', - 'Add Files', - 'Add unversioned files', - self.icon('menuadd.ico')) - item.connect('activate', self._add_cb, vfs_files) - submenu.append_item(item) - - if modified or added or removed or deleted or unknown: - item = nautilus.MenuItem('HgNautilus::40commit', - 'Commit Files', - 'Commit changes', - self.icon('menucommit.ico')) - item.connect('activate', self._commit_cb, vfs_files) - submenu.append_item(item) - - item = nautilus.MenuItem('HgNautilus::50revert', - 'Undo Changes', - 'Revert changes to files', - self.icon('menurevert.ico')) - item.connect('activate', self._revert_cb, vfs_files) - submenu.append_item(item) - - if modified or clean: - item = nautilus.MenuItem('HgNautilus::60log', - 'File Changelog', - 'Show file revision history', - self.icon('menulog.ico')) - item.connect('activate', self._history_cb, vfs_files) - item = nautilus.MenuItem('HgNautilus::annotate', - 'Annotate File', - 'Annotate file at current revision', - self.icon('menulog.ico')) - item.connect('activate', self._datamine_cb, vfs_files) - submenu.append_item(item) - - - if modified: - item = nautilus.MenuItem('HgNautilus::70diff', - 'File Diffs', - 'Show file changes', - self.icon('menudiff.ico')) - item.connect('activate', self._diff_cb, vfs_files) - submenu.append_item(item) - - return mainitem, + '''Build context menu for selected files/directories''' + if vfs_files and self.menu: + return self.buildMenu(vfs_files, False)     def get_columns(self):   return nautilus.Column("HgNautilus::80hg_status", @@ -436,64 +254,50 @@
  "Version control status"),     def _get_file_status(self, repo, localpath): - emblem = None - status = '?' - - # This is not what the API is optimized for, but this appears - # to work efficiently enough - matcher = match.always(repo.root, localpath) - changes = repo.dirstate.status(matcher, True, True, True) - (lookup, modified, added, removed, deleted, unknown, - ignored, clean) = changes - - if localpath in clean: - emblem = 'default' - status = 'clean' - elif localpath in modified: - emblem = 'cvs-modified' - status = 'modified' - elif localpath in added: - emblem = 'cvs-aded' - status = 'added' - elif localpath in unknown: - emblem = 'new' - status = 'unrevisioned' - elif localpath in ignored: - status = 'ignored' - elif localpath in deleted: - # Should be hard to reach this state - emblem = 'stockmail-priority-high' - status = 'deleted' + from tortoise import cachethg + cachestate = cachethg.get_state(localpath, repo) + cache2state = {cachethg.UNCHANGED: ('default', 'clean'), + cachethg.ADDED: ('cvs-added', 'added'), + cachethg.MODIFIED: ('cvs-modified', 'modified'), + cachethg.UNKNOWN: ('new', 'unrevisioned'), + cachethg.IGNORED: (None, 'ignored'), + cachethg.NOT_IN_REPO: (None, '')} + emblem, status = cache2state.get(cachestate, (None, '?'))   return emblem, status   + def update_file_info(self, file): + '''Queue file for emblem and hg status update''' + self.scanStack.append(file) + if len(self.scanStack) == 1: + gobject.idle_add(self.fileinfo_on_idle)   - def update_file_info(self, file): - '''Return emblem and hg status for this file''' - path = self.get_path_for_vfs_file(file) - if path is None or file.is_directory(): - return - repo = self.get_repo_for_path(path) - if repo is None: - return - localpath = path[len(repo.root)+1:] - emblem, status = self._get_file_status(repo, localpath) + def fileinfo_on_idle(self): + '''Update emblem and hg status for files when there is time''' + if not self.scanStack: + return False + vfs_file = self.scanStack.pop() + path = self.get_path_for_vfs_file(vfs_file) + if not path: + return True + emblem, status = self._get_file_status(self.cacherepo, path)   if emblem is not None: - file.add_emblem(emblem) - file.add_string_attribute('hg_status', status) + vfs_file.add_emblem(emblem) + vfs_file.add_string_attribute('hg_status', status) + return True     # property page borrowed from http://www.gnome.org/~gpoo/hg/nautilus-hg/ - def __add_row(self, table, row, label_item, label_value): + def __add_row(self, row, label_item, label_value):   label = gtk.Label(label_item)   label.set_use_markup(True)   label.set_alignment(1, 0) - table.attach(label, 0, 1, row, row + 1, gtk.FILL, gtk.FILL, 0, 0) + self.table.attach(label, 0, 1, row, row + 1, gtk.FILL, gtk.FILL, 0, 0)   label.show()     label = gtk.Label(label_value)   label.set_use_markup(True)   label.set_alignment(0, 1)   label.show() - table.attach(label, 1, 2, row, row + 1, gtk.FILL, 0, 0, 0) + self.table.attach(label, 1, 2, row, row + 1, gtk.FILL, 0, 0, 0)     def get_property_pages(self, vfs_files):   if len(vfs_files) != 1: @@ -505,9 +309,8 @@
  repo = self.get_repo_for_path(path)   if repo is None:   return -   localpath = path[len(repo.root)+1:] - emblem, status = self._get_file_status(repo, localpath) + emblem, status = self._get_file_status(repo, path)     # Get the information from Mercurial   ctx = repo.changectx(None).parents()[0] @@ -528,22 +331,21 @@
    self.property_label = gtk.Label('Mercurial')   - table = gtk.Table(7, 2, False) - table.set_border_width(5) - table.set_row_spacings(5) - table.set_col_spacings(5) + self.table = gtk.Table(7, 2, False) + self.table.set_border_width(5) + self.table.set_row_spacings(5) + self.table.set_col_spacings(5)   - self.__add_row(table, 0, '<b>Status</b>:', status) - self.__add_row(table, 1, '<b>Last-Commit-Revision</b>:', str(rev)) - self.__add_row(table, 2, '<b>Last-Commit-Description</b>:', description) - self.__add_row(table, 3, '<b>Last-Commit-Date</b>:', date) - self.__add_row(table, 4, '<b>Last-Commit-User</b>:', user) + self.__add_row(0, '<b>Status</b>:', status) + self.__add_row(1, '<b>Last-Commit-Revision</b>:', str(rev)) + self.__add_row(2, '<b>Last-Commit-Description</b>:', description) + self.__add_row(3, '<b>Last-Commit-Date</b>:', date) + self.__add_row(4, '<b>Last-Commit-User</b>:', user)   if tags: - self.__add_row(table, 5, '<b>Tags</b>:', tags) + self.__add_row(5, '<b>Tags</b>:', tags)   if branch != 'default': - self.__add_row(table, 6, '<b>Branch</b>:', branch) + self.__add_row(6, '<b>Branch</b>:', branch)   - table.show() - + self.table.show()   return nautilus.PropertyPage("MercurialPropertyPage::status", - self.property_label, table), + self.property_label, self.table),
Change 1 of 1 Show Entire File hggtk/​__init__.py Stacked
 
1
2
3
4
5
6
7
8
9
 
 
 
 
 
 
 
 
 
 
@@ -1,9 +0,0 @@
-import pygtk -pygtk.require('2.0') -import gtk - -# Default icon for apps which do not set one -from shlib import get_tortoise_icon -icon = get_tortoise_icon("hg.ico") -if icon: - gtk.window_set_default_icon_from_file(icon)
Change 1 of 1 Show Entire File hggtk/​about.py Stacked
 
68
69
70
71
 
 
 
 
 
 
 
72
73
74
75
76
 
68
69
70
 
71
72
73
74
75
76
77
78
 
79
80
81
@@ -68,9 +68,14 @@
  thg_logo = os.path.normpath(shlib.get_tortoise_icon('thg_logo_92x50.png'))   thg_icon = os.path.normpath(shlib.get_tortoise_icon('thg_logo.ico'))   prog_root = os.path.dirname(os.path.dirname(os.path.dirname(thg_icon))) - license_file = os.path.join(prog_root, "COPYING.txt") + try: + license_file = os.path.join(prog_root, "COPYING.txt") + self.set_license(file(license_file).read()) + except IOError: + import hgtk + license = hgtk.shortlicense.splitlines()[1:] + self.set_license('\n'.join(license))   - self.set_license(file(license_file).read())   self.set_comments("with " + lib_versions + "\n\n" + comment)   self.set_logo(gtk.gdk.pixbuf_new_from_file(thg_logo))   self.set_icon_from_file(thg_icon)
Change 1 of 4 Show Entire File hggtk/​hgtk.py Stacked
renamed from contrib/hgtk
 
56
57
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
60
61
 
89
90
91
 
 
 
 
 
92
93
94
 
249
250
251
252
 
253
254
255
 
500
501
502
503
 
504
505
506
 
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
 
103
104
105
106
107
108
109
110
111
112
113
 
268
269
270
 
271
272
273
274
 
519
520
521
 
522
523
524
525
@@ -56,6 +56,20 @@
  u.print_exc()   raise   +def get_list_from_file(filename): + try: + if filename == '-': + lines = [ x.replace("\n", "") for x in sys.stdin.readlines() ] + else: + fd = open(filename, "r") + lines = [ x.replace("\n", "") for x in fd.readlines() ] + fd.close() + os.unlink(filename) + return lines + except IOError, e: + sys.stderr.write(_('can not read file "%s". Ignored.\n') % filename) + return [] +  def _parse(ui, args):   options = {}   cmdoptions = {} @@ -89,6 +103,11 @@
  options[n] = cmdoptions[n]   del cmdoptions[n]   + listfile = options.get('listfile') + if listfile: + del options['listfile'] + args += get_list_from_file(listfile) +   return (cmd, cmd and i[0] or None, args, options, cmdoptions)    def _runcatch(ui, args): @@ -249,7 +268,7 @@
 def datamine(ui, *pats, **opts):   """repository search and annotate tool"""   from hggtk.datamine import run - opts['files'] = sys.argv[2:] or [] + opts['files'] = pats or []   opts['cwd'] = os.getcwd()   run(**opts)   @@ -500,7 +519,7 @@
  _('repository root directory or symbolic path name')),   ('v', 'verbose', None, _('enable additional output')),   ('h', 'help', None, _('display help and exit')), - ('', 'debugger', None, _('start debugger')), + ('l', 'listfile', '', _('read file list from file')),  ]    table = {
Change 1 of 1 Show Entire File hggtk/​shlib.py Stacked
 
181
182
183
184
 
185
186
 
 
 
187
188
189
 
181
182
183
 
184
185
 
186
187
188
189
190
191
@@ -181,9 +181,11 @@
  # Else try relative paths from hggtk, the repository layout   fdir = os.path.dirname(__file__)   paths.append(os.path.join(fdir, '..', 'icons')) - # ... or the source installer layout + # ... or the unix installer layout   paths.append(os.path.join(fdir, '..', '..', '..', - 'share', 'tortoisehg', 'icons')) + 'share', 'pixmaps', 'tortoisehg', 'icons')) + paths.append(os.path.join(fdir, '..', '..', '..', '..', + 'share', 'pixmaps', 'tortoisehg', 'icons'))   except NameError: # __file__ is not always available   pass   for p in paths:
Show Entire File hggtk/​status.py Stacked
(No changes)
Change 1 of 1 Show Entire File hgproc.bat Stacked
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
@@ -1,20 +0,0 @@
-:: -:: Win32 batch file to handle TortoiseHg external proc calls -:: - -@echo off -setlocal - -:: Look in the registry for TortoiseHg location -for /f "skip=2 tokens=3*" %%A in ( - '"reg query "HKEY_LOCAL_MACHINE\SOFTWARE\TortoiseHg" /ve 2> nul"' ) do set TortoisePath=%%B -if "%TortoisePath%"=="" (goto :notfound) else (goto :hgproc) - -:hgproc -python "%TortoisePath%\hgproc.py" %* -goto end - -:notfound -echo hgproc: cannot find TortoiseHg location in the registry. - -:end \ No newline at end of file
Change 1 of 1 Show Entire File hgproc.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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
@@ -1,132 +0,0 @@
-# -# front-end for TortoiseHg dialogs -# -# Copyright (C) 2007 TK Soh <teekaysoh@gmail.com> -# - -import pygtk -pygtk.require('2.0') -import gtk - -import os -import sys - -from mercurial import demandimport; demandimport.enable() -from mercurial import ui -from tortoise.thgutil import get_prog_root - -# always use hg exe installed with TortoiseHg -thgdir = get_prog_root() -try: - os.environ['PATH'] = os.path.pathsep.join([thgdir, os.environ['PATH']]) -except KeyError: - os.environ['PATH'] = thgdir - -if not sys.stdin.isatty(): - try: - import win32traceutil - - # FIXME: quick workaround traceback caused by missing "closed" - # attribute in win32trace. - from mercurial import ui - def write_err(self, *args): - for a in args: - sys.stderr.write(str(a)) - ui.ui.write_err = write_err - except ImportError: - pass - except pywintypes.error: - pass - -# Map hgproc commands to dialog modules in hggtk/ -from hggtk import commit, status, addremove, tagadd, tags, history, merge -from hggtk import diff, revisions, update, serve, clone, synch, hgcmd, about -from hggtk import recovery, thgconfig, datamine, hginit, thgshelve, rename -from hggtk import hgignore -_dialogs = { 'commit' : commit, 'status' : status, 'revert' : status, - 'add' : addremove, 'remove' : addremove, 'tag' : tagadd, - 'tags' : tags, 'log' : history, 'history': history, - 'diff' : diff, 'merge' : merge, 'tip' : revisions, - 'parents': revisions, 'heads' : revisions, 'update' : update, - 'clone' : clone, 'serve' : serve, 'synch' : synch, - 'about' : about, 'config' : thgconfig, 'recovery': recovery, - 'datamine': datamine, 'init' : hginit, 'shelve' : thgshelve, - 'hgignore': hgignore, 'rename' : rename } - -def get_list_from_file(filename): - fd = open(filename, "r") - lines = [ x.replace("\n", "") for x in fd.readlines() ] - fd.close() - return lines - -def get_option(args): - import getopt - long_opt_list = ('command=', 'exepath=', 'listfile=', 'root=', 'cwd=', - 'deletelistfile', 'nogui', 'detect') - opts, args = getopt.getopt(args, "c:e:l:dR:", long_opt_list) - # Set default options - options = {} - options['hgcmd'] = 'help' - options['cwd'] = os.getcwd() - options['files'] = [] - options['gui'] = True - listfile = None - delfile = False - - for o, a in opts: - if o in ("-c", "--command"): - options['hgcmd'] = a - elif o in ("-l", "--listfile"): - listfile = a - elif o in ("-d", "--deletelistfile"): - delfile = True - elif o in ("--nogui"): - options['gui'] = False - elif o in ("-R", "--root"): - options['root'] = a - elif o in ("--cwd"): - options['cwd'] = a - elif o in ("--detect"): - options['detect'] = True - - if listfile: - options['files'] = get_list_from_file(listfile) - if delfile: - os.unlink(listfile) - - return (options, args) - -def parse(args): - option, args = get_option(args) - - cmdline = ['hg', option['hgcmd']] - if 'root' in option: - cmdline.append('--repository') - cmdline.append(option['root']) - cmdline.extend(args) - cmdline.extend(option['files']) - option['cmdline'] = cmdline - - global _dialogs - dialog = _dialogs.get(option['hgcmd'], hgcmd) - dialog.run(**option) - - -def run_trapped(args): - try: - dlg = parse(sys.argv[1:]) - except: - import traceback - from hggtk.dialog import error_dialog - tr = traceback.format_exc() - print tr - error_dialog(None, "Error executing hgproc", tr) - -if __name__=='__main__': - #dlg = parse(['-c', 'help', '--', '-v']) - #dlg = parse(['-c', 'log', '--root', 'c:\hg\h1', '--', '-l1']) - #dlg = parse(['-c', 'status', '--root', 'c:\hg\h1', ]) - #dlg = parse(['-c', 'add', '--root', 'c:\hg\h1', '--listfile', 'c:\\hg\\h1\\f1', '--notify']) - #dlg = parse(['-c', 'rollback', '--root', 'c:\\hg\\h1']) - print "hgproc sys.argv =", sys.argv - dlg = run_trapped(sys.argv[1:])
Change 1 of 1 Show Entire File hgtk 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
@@ -0,0 +1,51 @@
+#!/usr/bin/env python +# +# front-end script for TortoiseHg dialogs +# +# Copyright (C) 2008-9 Steve Borho <steve@borho.org> +# Copyright (C) 2008 TK Soh <teekaysoh@gmail.com> +# + +shortlicense = '''\ +Copyright (C) 2009 Steve Borho <steve@borho.org>. +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +''' + +import pygtk +pygtk.require('2.0') +import gtk + +from mercurial import demandimport; demandimport.enable() +import sys + +if not hasattr(sys, "frozen"): + import os + # check if hggtk is a symlink first + pfile = __file__ + if pfile.endswith('.pyc'): + pfile = pfile[:-1] + thgpath = os.path.dirname(os.path.realpath(pfile)) + testpath = os.path.join(thgpath, 'hggtk') + if os.path.isdir(testpath): + if thgpath not in sys.path: + sys.path.insert(0, thgpath) + else: + # try environment + thgpath = os.environ.get('TORTOISEHG_PATH') + if thgpath: + thgpath = os.path.normpath(os.path.expanduser(thgpath)) + if os.path.exists(thgpath) and thgpath not in sys.path: + sys.path.insert(0, thgpath) + +# else assume tortoise is already in PYTHONPATH +try: + import hggtk.hgtk +except ImportError: + sys.stderr.write("abort: couldn't find hggtk libraries in [%s]\n" % + ' '.join(sys.path)) + sys.stderr.write("(check your install and PYTHONPATH)\n") + sys.exit(-1) + +sys.exit(hggtk.hgtk.dispatch(sys.argv[1:])) +
Change 1 of 6 Show Entire File setup.py Stacked
 
1
2
3
 
4
5
6
7
8
9
 
 
 
10
11
12
13
14
15
16
17
18
19
 
54
55
56
57
58
59
60
61
 
84
85
86
87
 
88
89
90
 
97
98
99
 
100
101
102
103
104
105
 
106
107
108
 
111
112
113
114
115
116
117
 
125
126
127
128
 
129
130
131
 
1
2
 
3
4
 
 
 
 
 
5
6
7
8
9
10
11
12
13
 
14
15
16
 
51
52
53
 
 
54
55
56
 
79
80
81
 
82
83
84
85
 
92
93
94
95
96
97
 
 
 
 
98
99
100
101
 
104
105
106
 
107
108
109
 
117
118
119
 
120
121
122
123
@@ -1,19 +1,16 @@
 # setup.py  # A distutils setup script to install TortoiseHg in Windows and Posix -# environments. In Windows, it will register TortoiseHG COM server. +# environments.  # -# For Windows: -# To build stand-alone package, use 'python setup.py py2exe' then use -# InnoSetup to build the installer. By default, the installer will be -# created as dist\Output\setup.exe. -# +# On Windows, this script is mostly used to build a stand-alone +# TortoiseHg package. See installer\build.txt for details. The other +# use is to report the current version of the TortoiseHg source.    import time  import sys  import os  from distutils.core import setup   -  def setup_windows():   # Specific definitios for Windows NT-alike installations   _scripts = [] @@ -54,8 +51,6 @@
  _data_files = [(root, [os.path.join(root, file_) for file_ in files])   for root, dirs, files in os.walk('icons')]   extra['windows'] = [ - {"script":"hgproc.py", - "icon_resources": [(1, "icons/tortoise/hg.ico")]},   {"script":"tracelog.py",   "icon_resources": [(1, "icons/tortoise/python.ico")]}   ] @@ -84,7 +79,7 @@
 def setup_posix():   # Specific definitios for Posix installations   _extra = {} - _scripts = ['contrib/hgtk', 'hgproc.py'] + _scripts = ['hgtk']   _packages = ['hggtk', 'hggtk.vis', 'hggtk.iniparse', 'tortoise']   _data_files = [(os.path.join('share/pixmaps/tortoisehg', root),   [os.path.join(root, file_) for file_ in files]) @@ -97,12 +92,10 @@
   if os.name == "nt":   (scripts, packages, data_files, extra) = setup_windows() + desc='Windows shell extension for Mercurial VCS',  else:   (scripts, packages, data_files, extra) = setup_posix() - - -# specify version string, otherwise 'hg identify' will be used: -version = '' + desc='TortoiseHg dialogs for Mercurial VCS',    try:   l = os.popen('hg id -it').read().split() @@ -111,7 +104,6 @@
  version = l and l[-1] or 'unknown' # latest tag or revision number   if version.endswith('+'):   version += time.strftime('%Y%m%d') -  except OSError:   version = "unknown"   @@ -125,7 +117,7 @@
  author='TK Soh',   author_email='teekaysoh@gmail.com',   url='http://bitbucket.org/tortoisehg/stable/', - description='Windows shell extension for Mercurial VCS', + description=desc,   license='GNU GPL2',   scripts=scripts,   packages=packages,
Change 1 of 1 Show Entire File tortoise/​cachethg.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
@@ -0,0 +1,174 @@
+import os +from mercurial import hg, cmdutil, util, ui +import thgutil +import sys +try: + from mercurial.error import RepoError +except ImportError: + from mercurial.repo import RepoError + +try: + from win32api import GetTickCount + CACHE_TIMEOUT = 5000 +except ImportError: + from time import time as GetTickCount + CACHE_TIMEOUT = 5.0 + +UNCHANGED = "unchanged" +ADDED = "added" +MODIFIED = "modified" +UNKNOWN = "unknown" +IGNORED = "ignored" +NOT_IN_REPO = "n/a" +ROOT = "root" + +# file status cache +overlay_cache = {} +cache_tick_count = 0 +cache_root = None +cache_pdir = None + + +def add_dirs(list): + dirs = set() + if list: + dirs.add('') + for f in list: + pdir = os.path.dirname(f) + if pdir in dirs: + continue + while pdir: + dirs.add(pdir) + pdir = os.path.dirname(pdir) + list.extend(dirs) + + +def get_state(upath, repo=None): + """ + Get the state of a given path in source control. + """ + return get_states(upath, repo)[-1] + + +def get_states(upath, repo=None): + """ + Get the states of a given path in source control. + """ + global overlay_cache, cache_tick_count + global cache_root, cache_pdir + + #print "called: _get_state(%s)" % path + tc = GetTickCount() + + try: + # handle some Asian charsets + path = upath.encode('mbcs') + except: + path = upath + # check if path is cached + pdir = os.path.dirname(path) + if cache_pdir == pdir and overlay_cache: + if tc - cache_tick_count < CACHE_TIMEOUT: + status = overlay_cache.get(path) + if not status: + if os.path.isdir(os.path.join(path, '.hg')): + add(path, ROOT) + status = ROOT, + else: + status = overlay_cache.get(pdir, [NOT_IN_REPO]) + print "%s: %s (cached)" % (path, status) + return status + else: + print "Timed out!! ", + overlay_cache.clear() + cache_tick_count = GetTickCount() + # path is a drive + if path.endswith(":\\"): + add(path, NOT_IN_REPO) + return [NOT_IN_REPO] + # open repo + if cache_pdir == pdir: + root = cache_root + else: + print "find new root ", + root = thgutil.find_root(path) + if root == path: + add(path, ROOT) + return [ROOT] + cache_root = root + cache_pdir = pdir + + print "_get_state: root = ", root + if root is None: + print "_get_state: not in repo" + overlay_cache = {None: None} + cache_tick_count = GetTickCount() + return [NOT_IN_REPO] + hgdir = os.path.join(root, '.hg', '') + if pdir == hgdir[:-1] or pdir.startswith(hgdir): + add(pdir, NOT_IN_REPO) + return [NOT_IN_REPO] + try: + tc1 = GetTickCount() + if not repo or (repo.root != root and repo.root != os.path.realpath(root)): + repo = hg.repository(ui.ui(), path=root) + print "hg.repository() took %g ticks" % (GetTickCount() - tc1) + # check if to display overlay icons in this repo + overlayopt = repo.ui.config('tortoisehg', 'overlayicons', ' ').lower() + print "%s: repo overlayicons = " % path, overlayopt + if overlayopt == 'localdisk': + overlayopt = bool(thgutil.netdrive_status(path)) + if not overlayopt or overlayopt in 'false off no'.split(): + print "%s: overlayicons disabled" % path + overlay_cache = {None: None} + cache_tick_count = GetTickCount() + return [NOT_IN_REPO] + except RepoError: + # We aren't in a working tree + print "%s: not in repo" % pdir + add(pdir, IGNORED) + return [IGNORED] + except StandardError, e: + print "error while handling %s:" % pdir + print e + add(pdir, UNKNOWN) + return [UNKNOWN] + # get file status + tc1 = GetTickCount() + + try: + matcher = cmdutil.match(repo, [pdir]) + repostate = repo.status(match=matcher, ignored=True, + clean=True, unknown=True) + except util.Abort, inst: + print "abort: %s" % inst + print "treat as unknown : %s" % path + return [UNKNOWN] + + print "status() took %g ticks" % (GetTickCount() - tc1) + modified, added, removed, deleted, unknown, ignored, clean = repostate + # cached file info + tc = GetTickCount() + overlay_cache = {} + for grp, st in ( + (ignored, IGNORED), + (unknown, UNKNOWN), + (clean, UNCHANGED), + (added, ADDED), + (removed, MODIFIED), + (deleted, MODIFIED), + (modified, MODIFIED)): + add_dirs(grp) + for f in grp: + fpath = os.path.join(root, os.path.normpath(f)) + add(fpath, st) + add(root, ROOT) + status = overlay_cache.get(path, [UNKNOWN]) + print "\n%s: %s" % (path, status) + cache_tick_count = GetTickCount() + return status + + +def add(path, state): + c = overlay_cache.setdefault(path, []) + c.append(state)
 
15
16
17
18
 
19
 
20
21
22
 
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
 
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
 
126
127
128
 
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
 
192
193
194
195
196
 
 
197
198
199
 
211
212
213
214
 
215
216
217
 
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
261
262
263
264
265
266
267
268
269
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
 
 
 
 
 
 
 
 
 
 
 
 
450
451
452
453
454
455
456
 
457
458
 
459
460
461
462
 
463
464
465
 
467
468
469
470
 
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
16
17
 
18
19
20
21
22
23
 
36
37
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
40
41
 
47
48
49
 
 
 
 
 
 
 
 
 
 
 
50
 
51
52
53
54
 
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
 
119
120
121
 
 
122
123
124
125
126
 
138
139
140
 
141
142
143
144
 
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
 
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
@@ -15,8 +15,9 @@
 import win32gui_struct  import win32api  import _winreg -from mercurial import hg +from mercurial import hg, util  from thgutil import * +import menuthg    try:   from mercurial.error import RepoError @@ -35,70 +36,6 @@
 S_OK = 0  S_FALSE = 1   -class TortoiseMenu(object): - def __init__(self, menutext, helptext, handler, icon=None, state=True): - self.menutext = menutext - self.helptext = helptext - self.handler = handler - self.state = state - self.icon = icon - -class TortoiseSubmenu(object): - def __init__(self, menutext, menus=[], icon=None): - self.menutext = menutext - self.menus = menus[:] - self.icon = icon - - def add_menu(self, menutext, helptext, handler, icon=None, state=True): - self.menus.append(TortoiseMenu(menutext, helptext, handler, icon, state)) - - def add_sep(self): - self.menus.append(TortoiseMenuSep()) - - def get_menus(self): - return self.menus - -class TortoiseMenuSep(object): - def __init__(self): - pass - -def open_repo(path): - root = find_root(path) - if root: - try: - repo = hg.repository(ui.ui(), path=root) - return repo - except RepoError: - pass - - return None - -def open_dialog(cmd, cmdopts='', cwd=None, root=None, filelist=[], gui=True): - app_path = find_path("hgproc", get_prog_root(), '.EXE;.BAT') - - if filelist: - fd, tmpfile = tempfile.mkstemp(prefix="tortoisehg_filelist_") - os.write(fd, "\n".join(filelist)) - os.close(fd) - - # start gpopen - gpopts = "--command %s" % cmd - if root: - gpopts += " --root %s" % shellquote(root) - if filelist: - gpopts += " --listfile %s --deletelistfile" % (shellquote(tmpfile)) - if cwd: - gpopts += " --cwd %s" % shellquote(cwd) - if not gui: - gpopts += " --nogui" - - cmdline = '%s %s -- %s' % (shellquote(app_path), gpopts, cmdopts) - - try: - run_program(cmdline) - except win32api.error, details: - win32ui.MessageBox("Error executing command - %s" % (details), "gpopen") -  def get_clone_repo_name(dir, repo_name):   dest_clone = os.path.join(dir, repo_name)   if os.path.exists(dest_clone): @@ -110,19 +47,8 @@
  i += 1   return dest_clone   -def run_program(cmdline): - print "run_program: %s" % (cmdline) - - import subprocess - pop = subprocess.Popen(cmdline, - shell=False, - creationflags=win32con.CREATE_NO_WINDOW, - stderr=subprocess.STDOUT, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE) -  """Windows shell extension that adds context menu items to Mercurial repository""" -class ContextMenuExtension: +class ContextMenuExtension(menuthg.menuThg):   _reg_progid_ = "Mercurial.ShellExtension.ContextMenu"   _reg_desc_ = "Mercurial Shell Extension"   _reg_clsid_ = "{EEE9936B-73ED-4D45-80C9-AF918354F885}" @@ -154,28 +80,29 @@
  ]     def __init__(self): - self._folder = None - self._filenames = [] - self._handlers = {} + self.folder = None + self.fnames = [] + self.menuitems = {} + menuthg.menuThg.__init__(self)     def Initialize(self, folder, dataobj, hkey):   if folder: - self._folder = shell.SHGetPathFromIDList(folder) + self.folder = shell.SHGetPathFromIDList(folder)     if dataobj:   format_etc = win32con.CF_HDROP, None, 1, -1, pythoncom.TYMED_HGLOBAL   sm = dataobj.GetData(format_etc)   num_files = shell.DragQueryFile(sm.data_handle, -1)   for i in range(num_files): - self._filenames.append(shell.DragQueryFile(sm.data_handle, i)) + self.fnames.append(shell.DragQueryFile(sm.data_handle, i))     def _create_menu(self, parent, menus, pos, idCmd, idCmdFirst):   for menu_info in menus: - if type(menu_info) == TortoiseMenuSep: + if menu_info.isSep():   win32gui.InsertMenu(parent, pos,   win32con.MF_BYPOSITION|win32con.MF_SEPARATOR,   idCmdFirst + idCmd, None) - elif type(menu_info) == TortoiseSubmenu: + elif menu_info.isSubmenu():   submenu = win32gui.CreatePopupMenu()   idCmd = self._create_menu(submenu, menu_info.get_menus(), 0,   idCmd, idCmdFirst) @@ -192,8 +119,8 @@
    item, _ = win32gui_struct.PackMENUITEMINFO(**opt)   win32gui.InsertMenuItem(parent, pos, True, item) - self._handlers[idCmd] = ("", lambda x,y: 0) - elif type(menu_info) == TortoiseMenu: + self.menuitems[idCmd] = ("", "") + else:   fstate = win32con.MF_BYCOMMAND   if menu_info.state is False:   fstate |= win32con.MF_GRAYED @@ -211,7 +138,7 @@
    item, _ = win32gui_struct.PackMENUITEMINFO(**opt)   win32gui.InsertMenuItem(parent, pos, True, item) - self._handlers[idCmd] = (menu_info.helptext, menu_info.handler) + self.menuitems[idCmd] = (menu_info.helptext, menu_info.hgcmd)   idCmd += 1   pos += 1   return idCmd @@ -220,246 +147,57 @@
  if uFlags & shellcon.CMF_DEFAULTONLY:   return 0   - thgmenu = [] # hg menus + thgmenu = []     # a brutal hack to detect if we are the first menu to go on to the   # context menu. If we are not the first, then add a menu separator   # The number '30000' is just a guess based on my observation   print "idCmdFirst = ", idCmdFirst   if idCmdFirst >= 30000: - thgmenu.append(TortoiseMenuSep()) - + thgmenu.append(menuthg.TortoiseMenuSep())   # As we are a context menu handler, we can ignore verbs. - self._handlers = {} - if self._folder and self._filenames: + + if self.folder: + cwd = self.folder + elif self.fnames: + f = self.fnames[0] + if len(self.fnames) == 1 and os.path.isdir(f): + cwd = f + self.fnames = [] + else: + cwd = os.path.dirname(f) + + self.menuitems = {} + if self.folder and self.fnames:   # get menus with drag-n-drop support - commands = self._get_commands_dragdrop() + thgmenu += self.get_commands_dragdrop(self.fnames, self.folder) + repo = menuthg.open_repo(self.folder)   else: - # add regularly used commit menu to main context menu - rpath = self._folder or self._filenames[0] - if open_repo(rpath): - thgmenu.append(TortoiseMenu(_("HG Commit..."), - _("Commit changes in repository"), - self._commit, icon="menucommit.ico")) - - # get other menus for hg submenu - commands = self._get_commands() - - # add common menu items - commands.append(TortoiseMenuSep()) - commands.append(TortoiseMenu(_("About"), - _("About TortoiseHg"), - self._about, icon="menuabout.ico")) - - if commands: - # create submenus with Hg commands - thgmenu.append(TortoiseSubmenu("TortoiseHG", commands, - icon="hg.ico")) - thgmenu.append(TortoiseMenuSep()) - - idCmd = self._create_menu(hMenu, thgmenu, indexMenu, 0, - idCmdFirst) - - # Return total number of menus & submenus we've added - return idCmd - else: - # no applicable Hg actions - return 0 - - def _get_commands_dragdrop(self): - """ - Get a list of commands valid for the current selection. - - Each command is a tuple containing (display text, handler). - """ - - print "_get_commands_dragdrop() on %s" % ", ".join(self._filenames) - - # we can only accept dropping one item - if len(self._filenames) > 1: - return [] - - # open repo - drag_repo = None - drop_repo = None - - print "drag = %s" % self._filenames[0] - print "drop = %s" % self._folder - - drag_path = self._filenames[0] - drag_repo = open_repo(drag_path) - if not drag_repo: - return [] - if drag_repo and drag_repo.root != drag_path: - return [] # dragged item must be a hg repo root directory - - drop_repo = open_repo(self._folder) - - result = [] - result.append(TortoiseMenu(_("Create Clone"), - _("Create clone here from source"), - self._clone_here, icon="menuclone.ico")) - - if drop_repo: - result.append(TortoiseMenu(_("Synchronize"), - _("Synchronize with dragged repository"), - self._synch_here, icon="menusynch.ico")) - return result - - def _get_commands(self): - """ - Get a list of commands valid for the current selection. - - Each command is a tuple containing (display text, handler). - """ - - print "_get_commands() on %s" % ", ".join(self._filenames) - - # open repo - result = [] - rpath = self._folder or self._filenames[0] - repo = open_repo(rpath) - if repo is None: - result.append(TortoiseMenu(_("Clone a Repository"), - _("clone a repository"), - self._clone, icon="menuclone.ico")) - result.append(TortoiseMenu(_("Create Repository Here"), - _("create a new repository in this directory"), - self._init, icon="menucreaterepos.ico", - state=os.path.isdir(rpath))) - else: - for f in self._filenames: - if f.endswith('.hgignore'): - result.append(TortoiseMenu(_("Edit Ignore Filter"), - _("Edit repository ignore filter"), - self._hgignore, icon="ignore.ico")) - break - - result.append(TortoiseMenu(_("View File Status"), - _("Repository status"), - self._status, icon="menushowchanged.ico")) - result.append(TortoiseMenu(_("Shelve Changes"), - _("Shelve or unshelve repository changes"), - self._shelve, icon="shelve.ico")) - - # Visual Diff (any extdiff command) - has_vdiff = repo.ui.config('tortoisehg', 'vdiff', '') != '' - result.append(TortoiseMenu(_("Visual Diff"), - _("View changes using GUI diff tool"), - self._vdiff, icon="TortoiseMerge.ico", - state=has_vdiff)) - - if len(self._filenames) == 0: - result.append(TortoiseMenu(_("Guess Renames"), - _("Detect renames and copies"), - self._guess_rename, icon="detect_rename.ico")) - elif len(self._filenames) == 1: - result.append(TortoiseMenu(_("Rename File"), - _("Rename file or directory"), - self._rename, icon="general.ico")) # needs ico - - result.append(TortoiseMenu(_("Add Files"), - _("Add files to Hg repository"), - self._add, icon="menuadd.ico")) - result.append(TortoiseMenu(_("Remove Files"), - _("Remove selected files on the next commit"), - self._remove, icon="menudelete.ico")) - result.append(TortoiseMenu(_("Undo Changes"), - _("Revert selected files"), - self._revert, icon="menurevert.ico")) - - # we can only annotate file but not directories - annotatible = len(self._filenames) > 0 - for f in self._filenames: - if not os.path.isfile(f): - annotatible = False - break - result.append(TortoiseMenu(_("Annotate Files"), - _("show changeset information per file line"), - self._annotate, icon="menublame.ico", - state=annotatible)) - - result.append(TortoiseMenuSep()) - result.append(TortoiseMenu(_("Update To Revision"), - _("update working directory"), - self._update, icon="menucheckout.ico")) - - can_merge = len(repo.heads()) > 1 and \ - len(repo.changectx(None).parents()) < 2 - result.append(TortoiseMenu(_("Merge Revisions"), - _("merge working directory with another revision"), - self._merge, icon="menumerge.ico", - state=can_merge)) - - in_merge = len(repo.changectx(None).parents()) > 1 - result.append(TortoiseMenu(_("Undo Merge"), - _("Undo merge by updating to revision"), - self._merge, icon="menuunmerge.ico", - state=in_merge)) - - result.append(TortoiseMenuSep()) - - result.append(TortoiseMenu(_("View Changelog"), - _("View revision history"), - self._history, icon="menulog.ico")) - - result.append(TortoiseMenu(_("Search Repository"), - _("Search revisions of files for a text pattern"), - self._grep, icon="menurepobrowse.ico")) - - if repo.ui.config('tortoisehg', 'view'): - result.append(TortoiseMenu(_("Revision Graph"), - _("View history with DAG graph"), - self._view, icon="menurevisiongraph.ico")) - - result.append(TortoiseMenuSep()) - - result.append(TortoiseMenu(_("Synchronize..."), - _("Synchronize with remote repository"), - self._synch, icon="menusynch.ico")) - result.append(TortoiseMenu(_("Recovery..."), - _("General repair and recovery of repository"), - self._recovery, icon="general.ico")) - result.append(TortoiseMenu(_("Web Server"), - _("start web server for this repository"), - self._serve, icon="proxy.ico")) - - result.append(TortoiseMenuSep()) - result.append(TortoiseMenu(_("Create Clone"), - _("Clone a repository here"), - self._clone, icon="menuclone.ico")) - can_init = repo.root != rpath and os.path.isdir(rpath) - result.append(TortoiseMenu(_("Create Repository Here"), - _("create a new repository in this directory"), - self._init, icon="menucreaterepos.ico", - state=can_init)) - - # config setttings menu - result.append(TortoiseMenuSep()) - optmenu = TortoiseSubmenu(_("Settings"),icon="menusettings.ico") - optmenu.add_menu(_("Global"), - _("Configure user wide settings"), - self._config_user, icon="settings_user.ico") - if repo: - optmenu.add_menu(_("Repository"), - _("Configure settings local to this repository"), - self._config_repo, icon="settings_repo.ico") - result.append(optmenu) - - return result + # get menus for hg menu + repo = menuthg.open_repo(cwd) + if repo: + thgmenu += self.get_commands(repo, cwd, self.fnames) + else: + thgmenu += self.get_norepo_commands(cwd, self.fnames) + + self.cwd = cwd + self.repo = repo + idCmd = self._create_menu(hMenu, thgmenu, indexMenu, 0, idCmdFirst) + # Return total number of menus & submenus we've added + return idCmd     def InvokeCommand(self, ci):   mask, hwnd, verb, params, dir, nShow, hotkey, hicon = ci   if verb >> 16:   # This is a textual verb invocation... not supported.   return S_FALSE - if verb not in self._handlers: + if verb not in self.menuitems:   raise Exception("Unsupported command id %i!" % verb) - self._handlers[verb][1](hwnd) + self.run_dialog(self.menuitems[verb][1])     def GetCommandString(self, cmd, uFlags):   if uFlags & shellcon.GCS_VALIDATEA or uFlags & shellcon.GCS_VALIDATEW: - if cmd in self._handlers: + if cmd in self.menuitems:   return S_OK   return S_FALSE   if uFlags & shellcon.GCS_VERBA or uFlags & shellcon.GCS_VERBW: @@ -467,222 +205,42 @@
  if uFlags & shellcon.GCS_HELPTEXTA or uFlags & shellcon.GCS_HELPTEXTW:   # The win32com.shell implementation encodes the resultant   # string into the correct encoding depending on the flags. - return self._handlers[cmd][0] + return self.menuitems[cmd][0]   return S_FALSE   - def _commit(self, parent_window): - self._run_dialog('commit') - - def _config_user(self, parent_window): - self._run_dialog('config', noargs=True) - - def _config_repo(self, parent_window): - self._run_dialog('config') - - def _vdiff(self, parent_window): - '''[tortoisehg] vdiff = <any extdiff command>''' - diff = ui.ui().config('tortoisehg', 'vdiff', None) - if not diff: - msg = "You must configure tortoisehg.vdiff in your Mercurial.ini" - title = "Visual Diff Not Configured" - win32ui.MessageBox(msg, title, win32con.MB_OK|win32con.MB_ICONERROR) - return - targets = self._filenames or [self._folder] - root = find_root(targets[0]) - open_dialog(diff, root=root, filelist=targets, gui=False) - - def _view(self, parent_window): - '''[tortoisehg] view = [hgk | hgview]''' - view = ui.ui().config('tortoisehg', 'view', '') - if not view: - msg = "You must configure tortoisehg.view in your Mercurial.ini" - title = "Revision Graph Tool Not Configured" - win32ui.MessageBox(msg, title, win32con.MB_OK|win32con.MB_ICONERROR) - return - - targets = self._filenames or [self._folder] - root = find_root(targets[0]) - if view == 'hgview': - hgviewpath = find_path('hgview') - cmd = "%s --repository=%s" % \ - (shellquote(hgviewpath), shellquote(root)) - if len(self._filenames) == 1: - cmd += " --file=%s" % shellquote(self._filenames[0]) - run_program(cmd) - else: - if view == 'hgk': - open_dialog('view', root=root, gui=False) - else: - msg = "Revision graph viewer %s not recognized" % view - title = "Unknown history tool" - win32ui.MessageBox(msg, title, win32con.MB_OK|win32con.MB_ICONERROR) - - def _history(self, parent_window): - self._log(parent_window) - - def _clone_here(self, parent_window): - src = self._filenames[0] - dest = self._folder - repo_name = os.path.basename(src) - dest_clone = get_clone_repo_name(dest, repo_name) - cmdopts = "--verbose" - repos = [src, dest_clone] - open_dialog('clone', cmdopts, cwd=dest, filelist=repos) - - def _push_here(self, parent_window): - src = self._filenames[0] - dest = self._folder - msg = "Push changes from %s into %s?" % (src, dest) - title = "Mercurial: push" - rv = win32ui.MessageBox(msg, title, win32con.MB_OKCANCEL) - if rv == 2: - return - - cmdopts = "--verbose" - open_dialog('push', cmdopts, root=src, filelist=[dest]) - - def _pull_here(self, parent_window): - src = self._filenames[0] - dest = self._folder - msg = "Pull changes from %s?" % (src) - title = "Mercurial: pull" - rv = win32ui.MessageBox(msg, title, win32con.MB_OKCANCEL) - if rv == 2: - return - - cmdopts = "--verbose" - open_dialog('pull', cmdopts, root=src, filelist=[dest]) - - def _incoming_here(self, parent_window): - src = self._filenames[0] - dest = self._folder - cmdopts = "--verbose" - open_dialog('incoming', cmdopts, root=src, filelist=[dest]) - - def _outgoing_here(self, parent_window): - src = self._filenames[0] - dest = self._folder - cmdopts = "--verbose" - open_dialog('outgoing', cmdopts, root=src, filelist=[dest]) - - def _init(self, parent_window): - self._run_dialog('init') - - def _shelve(self, parent_window): - self._run_dialog('shelve') - - def _hgignore(self, parent_window): - self._run_dialog('hgignore') - - def _rename(self, parent_window): - src = self._filenames[0] - if self._folder: - cwd = self._folder - elif self._filenames: - f = self._filenames[0] - cwd = os.path.isdir(f) and f or os.path.dirname(f) - cmdopts = "--verbose" - open_dialog('rename', cmdopts, cwd=cwd, filelist=[src]) - - def _guess_rename(self, parent_window): - self._run_dialog('rename --detect') - - def _status(self, parent_window): - self._run_dialog('status') - - def _clone(self, parent_window): - self._run_dialog('clone', True) - - def _synch(self, parent_window): - self._run_dialog('synch', True) - - def _synch_here(self, parent_window): - self._run_dialog('synch', False) - - def _pull(self, parent_window): - self._run_dialog('pull', True) - - def _push(self, parent_window): - self._run_dialog('push', True) - - def _incoming(self, parent_window): - self._run_dialog('incoming', True) - - def _outgoing(self, parent_window): - self._run_dialog('outgoing', True) - - def _serve(self, parent_window): - self._run_dialog('serve', noargs=True) - - def _add(self, parent_window): - self._run_dialog('add', modal=True) - - def _remove(self, parent_window): - self._run_dialog('remove') - - def _revert(self, parent_window): - self._run_dialog('status') - - def _tip(self, parent_window): - self._run_dialog('tip', True) - - def _parents(self, parent_window): - self._run_dialog('parents', True) - - def _heads(self, parent_window): - self._run_dialog('heads', True) - - def _log(self, parent_window): - self._run_dialog('log', verbose=False) - - def _show_tags(self, parent_window): - self._run_dialog('tags', True, verbose=False) - - def _add_tag(self, parent_window): - self._run_dialog('tag', True, verbose=False) - - def _diff(self, parent_window): - self._run_dialog('diff') - - def _merge(self, parent_window): - self._run_dialog('merge', noargs=True) - - def _recovery(self, parent_window): - self._run_dialog('recovery') - - def _update(self, parent_window): - self._run_dialog('update', noargs=True) - - def _grep(self, parent_window): - # open datamine dialog with no file brings up a search tab - self._run_dialog('datamine', noargs=True) - - def _annotate(self, parent_window): - # open datamine dialog with files brings up the annotate - # tabs for each file - self._run_dialog('datamine') - - def _run_dialog(self, hgcmd, noargs=False, verbose=True, modal=False): - if self._folder: - cwd = self._folder - elif self._filenames: - f = self._filenames[0] - cwd = os.path.isdir(f) and f or os.path.dirname(f) - else: - win32ui.MessageBox("Can't get cwd!", 'Hg ERROR', - win32con.MB_OK|win32con.MB_ICONERROR) - return - - targets = self._filenames or [self._folder] - root = find_root(targets[0]) - filelist = [] - if noargs == False: - filelist = targets - cmdopts = "%s" % (verbose and "--verbose" or "") - open_dialog(hgcmd, cmdopts, cwd=cwd, root=root, filelist=filelist) - - def _help(self, parent_window): - open_dialog('help', '--verbose') - - def _about(self, parent_window): - open_dialog('about') + def run_dialog(self, hgcmd): + cwd = self.cwd + if self.repo: + # Convert filenames to be relative to cwd + files = [] + cwd_rel = cwd[len(repo.root+os.sep):] + for f in self.fnames: + cpath = util.canonpath(self.repo.root, cwd, f) + if cpath.startswith(cwd_rel): + cpath = cpath[len(cwd_rel):] + files.append(cpath) + else: + files.append(f) + self.fnames = files + gpopts = " %s" % hgcmd + if self.fnames: + fd, tmpfile = tempfile.mkstemp(prefix="tortoisehg_filelist_") + os.write(fd, "\n".join(self.fnames)) + os.close(fd) + gpopts += " --listfile %s" % (shellquote(tmpfile)) + app_path = find_path("hgtk", get_prog_root(), '.EXE;.BAT') + if not app_path: + app_path = find_path("hgtk", None, '.EXE;.BAT') + cmdline = shellquote(app_path) + gpopts + try: + import subprocess + pop = subprocess.Popen(cmdline, + shell=False, + cwd=cwd, + creationflags=win32con.CREATE_NO_WINDOW, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE) + except win32api.error, details: + win32ui.MessageBox("Error executing command - %s" % (details), + "gpopen")
 
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 
41
42
43
 
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
 
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
 
240
241
242
243
244
245
 
 
 
 
25
26
27
 
 
 
 
 
 
 
 
 
 
 
 
 
28
29
30
31
 
33
34
35
 
 
 
 
 
 
 
 
 
 
36
37
38
 
61
62
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
65
66
67
68
 
69
70
71
72
 
101
102
103
 
 
 
104
105
106
@@ -25,19 +25,7 @@
  sys.stderr.write(str(a))  ui.ui.write_err = write_err   -# file/directory status -UNCHANGED = "unchanged" -ADDED = "added" -MODIFIED = "modified" -UNKNOWN = "unknown" -NOT_IN_REPO = "n/a" - -# file status cache -CACHE_TIMEOUT = 5000 -overlay_cache = {} -cache_tick_count = 0 -cache_root = None -cache_pdir = None +import cachethg    cache_lock = threading.Semaphore()   @@ -45,16 +33,6 @@
 S_OK = 0  S_FALSE = 1   -def add_dirs(list): - dirs = set() - for f in list: - dir = os.path.dirname(f) - if dir in dirs: - continue - while dir: - dirs.add(dir) - dir = os.path.dirname(dir) - list.extend(dirs)    class IconOverlayExtension(object):   """ @@ -83,129 +61,12 @@
  def GetPriority(self):   return 0   - def _get_state(self, upath): - """ - Get the state of a given path in source control. - """ - global overlay_cache, cache_tick_count - global cache_root, cache_pdir - - #print "called: _get_state(%s)" % path - tc = win32api.GetTickCount() - - try: - # handle some Asian charsets - path = upath.encode('mbcs') - except: - path = upath - - # check if path is cached - pdir = os.path.dirname(path) - if cache_pdir == pdir and overlay_cache: - if tc - cache_tick_count < CACHE_TIMEOUT: - try: - status = overlay_cache[path] - except: - status = UNKNOWN - print "%s: %s (cached)" % (path, status) - return status - else: - print "Timed out!!" - overlay_cache.clear() - - # path is a drive - if path.endswith(":\\"): - overlay_cache[path] = UNKNOWN - return NOT_IN_REPO - - # open repo - if cache_pdir == pdir: - root = cache_root - else: - print "find new root from", path, "was", cache_pdir - cache_pdir = pdir - cache_root = root = thgutil.find_root(pdir) - print "_get_state: new root = ", root - if root is None: - print "_get_state: not in repo" - overlay_cache = {None : None} - cache_tick_count = win32api.GetTickCount() - return NOT_IN_REPO - - try: - tc1 = win32api.GetTickCount() - repo = hg.repository(ui.ui(), path=root) - print "hg.repository() took %d ticks" % (win32api.GetTickCount() - tc1) - - # check if to display overlay icons in this repo - global_opts = ui.ui().configlist('tortoisehg', 'overlayicons', []) - repo_opts = repo.ui.configlist('tortoisehg', 'overlayicons', []) - - # print "%s: global overlayicons = " % path, global_opts - # print "%s: repo overlayicons = " % path, repo_opts - is_netdrive = thgutil.netdrive_status(path) is not None - if (is_netdrive and 'localdisks' in global_opts) \ - or 'False' in repo_opts: - print "%s: overlayicons disabled" % path - overlay_cache = {None : None} - cache_tick_count = win32api.GetTickCount() - return NOT_IN_REPO - except RepoError: - # We aren't in a working tree - print "%s: not in repo" % pdir - overlay_cache[path] = UNKNOWN - return NOT_IN_REPO - - # get file status - tc1 = win32api.GetTickCount() - - modified, added, removed, deleted = [], [], [], [] - unknown, ignored, clean = [], [], [] - try: - matcher = cmdutil.match(repo, [pdir]) - modified, added, removed, deleted, unknown, ignored, clean = \ - repo.status(match=matcher, ignored=True, - clean=True, unknown=True) - - # add directory status to list - for grp in (clean,modified,added,removed,deleted,ignored,unknown): - add_dirs(grp) - except util.Abort, inst: - print "abort: %s" % inst - print "treat as unknown : %s" % path - return UNKNOWN - - print "status() took %d ticks" % (win32api.GetTickCount() - tc1) - - # cached file info - tc = win32api.GetTickCount() - overlay_cache = {} - for grp, st in ( - (ignored, UNKNOWN), - (unknown, UNKNOWN), - (clean, UNCHANGED), - (added, ADDED), - (removed, MODIFIED), - (deleted, MODIFIED), - (modified, MODIFIED)): - for f in grp: - fpath = os.path.join(repo.root, os.path.normpath(f)) - overlay_cache[fpath] = st - - if path in overlay_cache: - status = overlay_cache[path] - else: - status = overlay_cache[path] = UNKNOWN - print "%s: %s" % (path, status) - cache_tick_count = win32api.GetTickCount() - return status -   def IsMemberOf(self, path, attrib):   global cache_lock   try:   cache_lock.acquire()   tc = win32api.GetTickCount() - if self._get_state(path) == self.state: + if cachethg.get_state(path) == self.state:   return S_OK   return S_FALSE   finally: @@ -240,6 +101,6 @@
  globals()[classname] = cls    _overlay_classes = [] -make_icon_overlay("Changed", "Modified", MODIFIED, "{4D0F33E1-654C-4A1B-9BE8-E47A98752BAB}") -make_icon_overlay("Unchanged", "Normal", UNCHANGED, "{4D0F33E2-654C-4A1B-9BE8-E47A98752BAB}") -make_icon_overlay("Added", "Added", ADDED, "{4D0F33E3-654C-4A1B-9BE8-E47A98752BAB}") +make_icon_overlay("Changed", "Modified", cachethg.MODIFIED, "{4D0F33E1-654C-4A1B-9BE8-E47A98752BAB}") +make_icon_overlay("Unchanged", "Normal", cachethg.UNCHANGED, "{4D0F33E2-654C-4A1B-9BE8-E47A98752BAB}") +make_icon_overlay("Added", "Added", cachethg.ADDED, "{4D0F33E3-654C-4A1B-9BE8-E47A98752BAB}")
Show Entire File tortoise/​menuthg.py Stacked
This file's diff was not loaded because this changeset is very large. Load changes
 
173
174
175
 
 
 
 
 
 
 
176
177
 
173
174
175
176
177
178
179
180
181
182
183
184
@@ -173,5 +173,12 @@
  path = os.environ.get('TORTOISEHG_PATH', defpath)   return os.path.isdir(path) and path or os.path.dirname(path)   + def netdrive_status(drive): + """ + return True if a network drive is accessible (connected, ...), + or None if <drive> is not a network drive + """ + return None +   def icon_to_bitmap(iconPathName):   pass