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

tortoise: add rpc server to support C++ shell extensions

- disable python-based overlay icons, which is handled by C++ extension
- keep context menus to support on-going development

Changeset 2fd8c418d9d8

Parent 9c6ba0878420

by TK Soh

Changes to 5 files · Browse files at 2fd8c418d9d8 Showing diff from parent 9c6ba0878420 Diff from another changeset...

Change 1 of 2 Show Entire File hgproc.py Stacked
 
13
14
15
16
 
17
18
19
 
103
104
105
 
 
 
 
 
106
107
108
 
13
14
15
 
16
17
18
19
 
103
104
105
106
107
108
109
110
111
112
113
@@ -13,7 +13,7 @@
   from mercurial import demandimport; demandimport.enable()  from mercurial import ui -from tortoise.thgutil import get_prog_root +from tortoise.thgutil import get_prog_root, find_root    # always use hg exe installed with TortoiseHg  thgdir = get_prog_root() @@ -103,6 +103,11 @@
  if 'root' in option:   cmdline.append('--repository')   cmdline.append(option['root']) + elif 'cwd' in option: + root = find_root(option['cwd']) + option['root'] = root + cmdline.append('--repository') + cmdline.append(option['root'])   cmdline.extend(args)   cmdline.extend(option['files'])   option['cmdline'] = cmdline
Change 1 of 1 Show Entire File tortoise/​rpcserver.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
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
@@ -0,0 +1,286 @@
+import os +import win32api +import win32con +from win32com.shell import shell, shellcon +import _winreg +from mercurial import hg, cmdutil, util +from mercurial import repo as _repo +import thgutil +import sys +import win32serviceutil +import win32service +import win32event +import win32pipe +import win32file +import pywintypes +import winerror + +PIPENAME = "\\\\.\\pipe\\PyPipeService" +PIPEBUFSIZE = 4096 + +# 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 + +# 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 + +# some misc constants +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) + +def get_hg_state(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 + + print "get_hg_state: path =", path + if not path: + return UNKNOWN + + # 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" + cache_pdir = pdir + cache_root = root = thgutil.find_root(pdir) + print "_get_state: 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 _repo.RepoError: + # We aren't in a working tree + print "%s: not in repo" % dir + overlay_cache[path] = UNKNOWN + return NOT_IN_REPO + + # get file status + tc1 = win32api.GetTickCount() + + modified, added, removed, deleted = [], [], [], [] + unknown, ignored, clean = [], [], [] + files = [] + 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 + +class PipeServer: + def __init__(self): + # Create an event which we will use to wait on. + # The "service stop" request will set this event. + self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) + + # We need to use overlapped IO for this, so we dont block when + # waiting for a client to connect. This is the only effective way + # to handle either a client connection, or a service stop request. + self.overlapped = pywintypes.OVERLAPPED() + + # And create an event to be used in the OVERLAPPED object. + self.overlapped.hEvent = win32event.CreateEvent(None,0,0,None) + + def SvcDoRun(self): + # We create our named pipe. + pipeName = PIPENAME + openMode = win32pipe.PIPE_ACCESS_DUPLEX | win32file.FILE_FLAG_OVERLAPPED + pipeMode = win32pipe.PIPE_TYPE_MESSAGE + + # When running as a service, we must use special security for the pipe + sa = pywintypes.SECURITY_ATTRIBUTES() + # Say we do have a DACL, and it is empty + # (ie, allow full access!) + sa.SetSecurityDescriptorDacl ( 1, None, 0 ) + + pipeHandle = win32pipe.CreateNamedPipe(pipeName, + openMode, + pipeMode, + win32pipe.PIPE_UNLIMITED_INSTANCES, + 0, 0, 6000, # default buffers, and 6 second timeout. + sa) + + # Loop accepting and processing connections + while 1: + try: + hr = win32pipe.ConnectNamedPipe(pipeHandle, self.overlapped) + except pywintypes.error, inst: + print "Error connecting pipe: ", inst + pipeHandle.Close() + break + + if hr==winerror.ERROR_PIPE_CONNECTED: + # Client is fast, and already connected - signal event + win32event.SetEvent(self.overlapped.hEvent) + # Wait for either a connection, or a service stop request. + timeout = win32event.INFINITE + waitHandles = self.hWaitStop, self.overlapped.hEvent + rc = win32event.WaitForMultipleObjects(waitHandles, 0, timeout) + if rc==win32event.WAIT_OBJECT_0: + # Stop event + break + else: + # read pipe and process request + try: + hr, data = win32file.ReadFile(pipeHandle, PIPEBUFSIZE) + message = "Processed %d bytes: '%s'" % (len(data), data) + print (message) + + if not data: + raise SystemExit # signal by dispatch terminate + + try: + data = data.decode('mbcs') + except: + pass + + try: + status = get_hg_state(data) + except SystemExit: + raise SystemExit # interrupted by thread2.terminate() + except: + import traceback + print "WARNING: something went wrong in get_hg_state()" + print traceback.format_exc() + status = "ERROR" + + win32file.WriteFile(pipeHandle, status) + + # And disconnect from the client. + win32pipe.DisconnectNamedPipe(pipeHandle) + except win32file.error: + # Client disconnected without sending data + # or before reading the response. + # Thats OK - just get the next connection + continue + +if __name__ == '__main__': + import sys + if '--server' in sys.argv: + svc = PipeServer() + svc.SvcDoRun() + elif '--client' in sys.argv: + for x in sys.argv[1:]: + if x.startswith('-'): + continue + path = os.path.abspath(x) + try: + status = win32pipe.CallNamedPipe(PIPENAME, path, PIPEBUFSIZE, 0) + except pywintypes.error, inst: + print "can't access named pipe '%s'" % PIPENAME + sys.exit() + print "%s = %s" % (path, status) + else: + print "usage:\n%s [--server|--client]" % sys.argv[0] + +
 
76
77
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
80
81
 
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
@@ -76,6 +76,28 @@
  os.environ['THG_ICON_PATH'] = os.path.join(dir, 'icons')   return dir   + def get_tortoise_icon(icon): + '''Find a tortoise icon, apply to PyGtk window''' + # The context menu should set this variable + var = os.environ.get('THG_ICON_PATH', None) + paths = var and [ var ] or [] + try: + # Else try relative paths from hggtk, the repository layout + dir = os.path.dirname(__file__) + paths.append(os.path.join(dir, '..', 'icons')) + # ... or the source installer layout + paths.append(os.path.join(dir, '..', '..', '..', + 'share', 'tortoisehg', 'icons')) + except NameError: # __file__ is not always available + pass + for p in paths: + path = os.path.join(p, 'tortoise', icon) + if os.path.isfile(path): + return path + else: + print 'icon not found', icon + return None +   def netdrive_status(drive):   """   return True if a network drive is accessible (connected, ...),
Change 1 of 1 Show Entire File tortoise/​thread2.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
@@ -0,0 +1,49 @@
+# Interuptible threads +# +# http://sebulba.wikispaces.com/recipe+thread2 + +import threading +import inspect +import ctypes + + +def _async_raise(tid, exctype): + """raises the exception, performs cleanup if needed""" + if not inspect.isclass(exctype): + raise TypeError("Only types can be raised (not instances)") + res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) + if res == 0: + raise ValueError("invalid thread id") + elif res != 1: + # """if it returns a number greater than one, you're in trouble, + # and you should call it again with exc=NULL to revert the effect""" + ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0) + raise SystemError("PyThreadState_SetAsyncExc failed") + + +class Thread(threading.Thread): + def _get_my_tid(self): + """determines this (self's) thread id""" + if not self.isAlive(): + raise threading.ThreadError("the thread is not active") + + # do we have it cached? + if hasattr(self, "_thread_id"): + return self._thread_id + + # no, look for it in the _active dict + for tid, tobj in threading._active.items(): + if tobj is self: + self._thread_id = tid + return tid + + raise AssertionError("could not determine the thread's id") + + def raise_exc(self, exctype): + """raises the given exception type in the context of this thread""" + _async_raise(self._get_my_tid(), exctype) + + def terminate(self): + """raises SystemExit in the context of the given thread, which should + cause the thread to exit silently (unless caught)""" + self.raise_exc(SystemExit)
Change 1 of 2 Show Entire File tortoisehg.py Stacked
 
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
 
122
123
124
125
126
127
128
129
130
131
132
 
48
49
50
 
 
 
51
52
53
54
55
 
 
 
56
57
58
 
116
117
118
 
 
 
 
 
119
120
121
@@ -48,17 +48,11 @@
 def DllRegisterServer():   check_tortoise_overlays()   RegisterServer(ContextMenuExtension) - RegisterServer(ChangedOverlay) - RegisterServer(AddedOverlay) - RegisterServer(UnchangedOverlay)   register_tortoise_path()    # for COM registration via py2exe  def DllUnregisterServer():   UnregisterServer(ContextMenuExtension) - UnregisterServer(ChangedOverlay) - UnregisterServer(AddedOverlay) - UnregisterServer(UnchangedOverlay)   register_tortoise_path(unregister=True)    def RegisterServer(cls): @@ -122,11 +116,6 @@
  register.UseCommandLine(ContextMenuExtension,   finalize_register = lambda: RegisterServer(ContextMenuExtension),   finalize_unregister = lambda: UnregisterServer(ContextMenuExtension)) - - for cls in (ChangedOverlay, AddedOverlay, UnchangedOverlay): - register.UseCommandLine(cls, - finalize_register = lambda: RegisterServer(cls), - finalize_unregister = lambda: UnregisterServer(cls))     if "--unregister" in sys.argv[1:]:   register_tortoise_path(unregister=True)