11
11
#import " PBGitRepository.h"
12
12
#import " PBEasyPipe.h"
13
13
#import " PBGitDefaults.h"
14
- #import " PBGitRepositoryWatcherEventPath.h"
15
14
16
15
NSString *PBGitRepositoryEventNotification = @" PBGitRepositoryModifiedNotification" ;
17
16
NSString *kPBGitRepositoryEventTypeUserInfoKey = @" kPBGitRepositoryEventTypeUserInfoKey" ;
18
17
NSString *kPBGitRepositoryEventPathsUserInfoKey = @" kPBGitRepositoryEventPathsUserInfoKey" ;
19
18
20
- @interface PBGitRepositoryWatcher ()
19
+ typedef void (^PBGitRepositoryWatcherCallbackBlock)(NSArray *changedFiles);
20
+
21
+ /* Small helper class to keep track of events */
22
+ @interface PBGitRepositoryWatcherEventPath : NSObject
23
+ @property NSString *path;
24
+ @property (assign ) FSEventStreamEventFlags flag;
25
+ @end
26
+
27
+ @implementation PBGitRepositoryWatcherEventPath
28
+ @end
29
+
30
+ @interface PBGitRepositoryWatcher () {
31
+ FSEventStreamRef eventStream;
32
+ NSDate *gitDirTouchDate;
33
+ NSDate *indexTouchDate;
34
+
35
+ BOOL _running;
36
+ }
37
+
38
+ @property (readonly ) NSString *gitDir;
39
+ @property (readonly ) NSString *workDir;
21
40
22
41
@property (nonatomic , strong ) NSMutableDictionary *statusCache;
23
42
@@ -32,101 +51,63 @@ void PBGitRepositoryWatcherCallback(ConstFSEventStreamRef streamRef,
32
51
void *_eventPaths,
33
52
const FSEventStreamEventFlags eventFlags[],
34
53
const FSEventStreamEventId eventIds[]){
35
- PBGitRepositoryWatcherCallbackBlock block = (__bridge PBGitRepositoryWatcherCallbackBlock)clientCallBackInfo;
36
-
37
- NSMutableArray *changePaths = [[NSMutableArray alloc ] init ];
54
+ PBGitRepositoryWatcher *watcher = (__bridge PBGitRepositoryWatcher *)clientCallBackInfo;
55
+
56
+ NSMutableArray *gitDirEvents = [NSMutableArray array ];
57
+ NSMutableArray *workDirEvents = [NSMutableArray array ];
38
58
NSArray *eventPaths = (__bridge NSArray *)_eventPaths;
39
59
for (int i = 0 ; i < numEvents; ++i) {
40
60
NSString *path = [eventPaths objectAtIndex: i];
41
61
PBGitRepositoryWatcherEventPath *ep = [[PBGitRepositoryWatcherEventPath alloc ] init ];
42
62
ep.path = [path stringByStandardizingPath ];
43
63
ep.flag = eventFlags[i];
44
- [changePaths addObject: ep];
45
64
65
+
66
+ if ([ep.path hasPrefix: watcher.gitDir]) {
67
+ // exclude all changes to .lock files
68
+ if ([ep.path hasSuffix: @" .lock" ]) {
69
+ continue ;
70
+ }
71
+ [gitDirEvents addObject: ep];
72
+ } else if ([ep.path hasPrefix: watcher.workDir]) {
73
+ [workDirEvents addObject: ep];
74
+ }
46
75
}
47
- if (block && changePaths.count ) {
48
- block (changePaths);
76
+
77
+ if (workDirEvents.count ) {
78
+ [watcher handleWorkDirEventCallback: workDirEvents];
79
+ }
80
+ if (gitDirEvents.count ) {
81
+ [watcher handleGitDirEventCallback: gitDirEvents];
49
82
}
50
83
}
51
84
52
85
@implementation PBGitRepositoryWatcher
53
86
54
- @synthesize repository;
87
+ - (instancetype ) initWithRepository : (PBGitRepository *)theRepository {
88
+ NSParameterAssert (theRepository != nil );
55
89
56
- - (id ) initWithRepository : (PBGitRepository *)theRepository {
57
90
self = [super init ];
58
91
if (!self) {
59
92
return nil ;
60
93
}
61
-
62
- __weak PBGitRepositoryWatcher* weakSelf = self;
63
- repository = theRepository;
64
-
65
- {
66
- gitDir = [repository.gtRepo.gitDirectoryURL.path stringByStandardizingPath ];
67
- if (!gitDir) {
68
- return nil ;
69
- }
70
- gitDirChangedBlock = ^(NSArray *changeEvents){
71
- NSMutableArray *filteredEvents = [NSMutableArray new ];
72
- for (PBGitRepositoryWatcherEventPath *event in changeEvents) {
73
- // exclude all changes to .lock files
74
- if ([event.path hasSuffix: @" .lock" ]) {
75
- continue ;
76
- }
77
- [filteredEvents addObject: event];
78
- }
79
- if (filteredEvents.count ) {
80
- [weakSelf handleGitDirEventCallback: filteredEvents];
81
- }
82
- };
83
- FSEventStreamContext gitDirWatcherContext = {0 , (__bridge void *)(gitDirChangedBlock), NULL , NULL , NULL };
84
- gitDirEventStream = FSEventStreamCreate (kCFAllocatorDefault , PBGitRepositoryWatcherCallback, &gitDirWatcherContext,
85
- (__bridge CFArrayRef)@[gitDir],
86
- kFSEventStreamEventIdSinceNow , 1.0 ,
87
- kFSEventStreamCreateFlagUseCFTypes |
88
- kFSEventStreamCreateFlagIgnoreSelf |
89
- kFSEventStreamCreateFlagFileEvents );
90
-
91
- }
92
- {
93
- workDir = repository.gtRepo .isBare ? nil : [repository.gtRepo.fileURL.path stringByStandardizingPath ];
94
- if (workDir) {
95
- workDirChangedBlock = ^(NSArray *changeEvents){
96
- NSMutableArray *filteredEvents = [NSMutableArray new ];
97
- PBGitRepositoryWatcher *watcher = weakSelf;
98
- if (!watcher) {
99
- return ;
100
- }
101
- for (PBGitRepositoryWatcherEventPath *event in changeEvents) {
102
- // exclude anything under the .git dir
103
- if ([event.path hasPrefix: watcher->gitDir]) {
104
- continue ;
105
- }
106
- [filteredEvents addObject: event];
107
- }
108
- if (filteredEvents.count ) {
109
- [watcher handleWorkDirEventCallback: filteredEvents];
110
- }
111
- };
112
- FSEventStreamContext workDirWatcherContext = {0 , (__bridge void *)(workDirChangedBlock), NULL , NULL , NULL };
113
- workDirEventStream = FSEventStreamCreate (kCFAllocatorDefault , PBGitRepositoryWatcherCallback, &workDirWatcherContext,
114
- (__bridge CFArrayRef)@[workDir],
115
- kFSEventStreamEventIdSinceNow , 1.0 ,
116
- kFSEventStreamCreateFlagUseCFTypes |
117
- kFSEventStreamCreateFlagIgnoreSelf |
118
- kFSEventStreamCreateFlagFileEvents );
119
- }
120
- }
121
-
122
94
123
- self.statusCache = [NSMutableDictionary new ];
95
+ _repository = theRepository;
96
+ _statusCache = [NSMutableDictionary new ];
124
97
125
98
if ([PBGitDefaults useRepositoryWatcher ])
126
99
[self start ];
127
100
return self;
128
101
}
129
102
103
+ - (void )dealloc {
104
+ if (eventStream) {
105
+ FSEventStreamStop (eventStream);
106
+ FSEventStreamInvalidate (eventStream);
107
+ FSEventStreamRelease (eventStream);
108
+ }
109
+ }
110
+
130
111
- (NSDate *) fileModificationDateAtPath : (NSString *)path {
131
112
NSError * error;
132
113
NSDictionary *attrs = [[NSFileManager defaultManager ] attributesOfItemAtPath: path
@@ -144,7 +125,7 @@ - (BOOL) indexChanged {
144
125
return NO ;
145
126
}
146
127
147
- NSDate *newTouchDate = [self fileModificationDateAtPath: [gitDir stringByAppendingPathComponent: @" index" ]];
128
+ NSDate *newTouchDate = [self fileModificationDateAtPath: [self . gitDir stringByAppendingPathComponent: @" index" ]];
148
129
if (![newTouchDate isEqual: indexTouchDate]) {
149
130
indexTouchDate = newTouchDate;
150
131
return YES ;
@@ -155,18 +136,19 @@ - (BOOL) indexChanged {
155
136
156
137
- (BOOL ) gitDirectoryChanged {
157
138
158
- for (NSURL * fileURL in [[NSFileManager defaultManager ] contentsOfDirectoryAtURL: repository.gitURL
159
- includingPropertiesForKeys: [NSArray arrayWithObject: NSURLContentModificationDateKey ]
160
- options: 0
161
-
162
- error: nil ])
139
+ NSArray *properties = @[NSURLIsDirectoryKey , NSURLContentModificationDateKey ];
140
+ NSArray <NSURL *> *urls = [[NSFileManager defaultManager ] contentsOfDirectoryAtURL: self .repository.gitURL
141
+ includingPropertiesForKeys: properties
142
+ options: 0
143
+ error: nil ];
144
+ for (NSURL *fileURL in urls)
163
145
{
164
- BOOL isDirectory = NO ;
165
- [[NSFileManager defaultManager ] fileExistsAtPath: [fileURL path ] isDirectory: &isDirectory];
166
- if (isDirectory)
146
+ NSNumber *number = nil ;
147
+ if (![fileURL getResourceValue: &number forKey: NSURLIsDirectoryKey error: nil ] || [number boolValue ]) {
167
148
continue ;
149
+ }
168
150
169
- NSDate * modTime = nil ;
151
+ NSDate * modTime = nil ;
170
152
if (![fileURL getResourceValue: &modTime forKey: NSURLContentModificationDateKey error: nil ])
171
153
continue ;
172
154
@@ -193,22 +175,22 @@ - (void) handleGitDirEventCallback:(NSArray *)eventPaths
193
175
NSMutableArray *paths = [NSMutableArray array ];
194
176
for (PBGitRepositoryWatcherEventPath *eventPath in eventPaths) {
195
177
// .git dir
196
- if ([eventPath.path isEqualToString: gitDir]) {
178
+ if ([eventPath.path isEqualToString: self . gitDir]) {
197
179
if ([self gitDirectoryChanged ] || eventPath.flag != kFSEventStreamEventFlagNone ) {
198
180
event |= PBGitRepositoryWatcherEventTypeGitDirectory;
199
181
[paths addObject: eventPath.path];
200
182
}
201
183
}
202
184
// ignore objects dir ... ?
203
- else if ([eventPath.path rangeOfString: [gitDir stringByAppendingPathComponent: @" objects" ]].location != NSNotFound ) {
185
+ else if ([eventPath.path rangeOfString: [self . gitDir stringByAppendingPathComponent: @" objects" ]].location != NSNotFound ) {
204
186
continue ;
205
187
}
206
188
// index is already covered
207
- else if ([eventPath.path rangeOfString: [gitDir stringByAppendingPathComponent: @" index" ]].location != NSNotFound ) {
189
+ else if ([eventPath.path rangeOfString: [self . gitDir stringByAppendingPathComponent: @" index" ]].location != NSNotFound ) {
208
190
continue ;
209
191
}
210
192
// subdirs of .git dir
211
- else if ([eventPath.path rangeOfString: gitDir].location != NSNotFound ) {
193
+ else if ([eventPath.path rangeOfString: self . gitDir].location != NSNotFound ) {
212
194
event |= PBGitRepositoryWatcherEventTypeGitDirectory;
213
195
[paths addObject: eventPath.path];
214
196
}
@@ -218,7 +200,7 @@ - (void) handleGitDirEventCallback:(NSArray *)eventPaths
218
200
NSDictionary *eventInfo = @{kPBGitRepositoryEventTypeUserInfoKey :@(event),
219
201
kPBGitRepositoryEventPathsUserInfoKey :paths};
220
202
221
- [[NSNotificationCenter defaultCenter ] postNotificationName: PBGitRepositoryEventNotification object: repository userInfo: eventInfo];
203
+ [[NSNotificationCenter defaultCenter ] postNotificationName: PBGitRepositoryEventNotification object: self . repository userInfo: eventInfo];
222
204
}
223
205
}
224
206
@@ -229,15 +211,15 @@ - (void)handleWorkDirEventCallback:(NSArray *)eventPaths
229
211
NSMutableArray *paths = [NSMutableArray array ];
230
212
for (PBGitRepositoryWatcherEventPath *eventPath in eventPaths) {
231
213
unsigned int fileStatus = 0 ;
232
- if (![eventPath.path hasPrefix: workDir]) {
214
+ if (![eventPath.path hasPrefix: self . workDir]) {
233
215
continue ;
234
216
}
235
- if ([eventPath.path isEqualToString: workDir]) {
217
+ if ([eventPath.path isEqualToString: self . workDir]) {
236
218
event |= PBGitRepositoryWatcherEventTypeWorkingDirectory;
237
219
[paths addObject: eventPath.path];
238
220
continue ;
239
221
}
240
- NSString *eventRepoRelativePath = [eventPath.path substringFromIndex: (workDir.length + 1 )];
222
+ NSString *eventRepoRelativePath = [eventPath.path substringFromIndex: (self . workDir.length + 1 )];
241
223
int ignoreResult = 0 ;
242
224
int ignoreError = git_status_should_ignore (&ignoreResult, self.repository .gtRepo .git_repository , eventRepoRelativePath.UTF8String );
243
225
if (ignoreError == GIT_OK && ignoreResult) {
@@ -262,25 +244,49 @@ - (void)handleWorkDirEventCallback:(NSArray *)eventPaths
262
244
NSDictionary *eventInfo = @{kPBGitRepositoryEventTypeUserInfoKey :@(event),
263
245
kPBGitRepositoryEventPathsUserInfoKey :paths};
264
246
265
- [[NSNotificationCenter defaultCenter ] postNotificationName: PBGitRepositoryEventNotification object: repository userInfo: eventInfo];
247
+ [[NSNotificationCenter defaultCenter ] postNotificationName: PBGitRepositoryEventNotification object: self . repository userInfo: eventInfo];
266
248
}
267
249
}
268
250
251
+ - (NSString *)gitDir {
252
+ return [self .repository.gtRepo.gitDirectoryURL.path stringByStandardizingPath ];
253
+ }
254
+
255
+ - (NSString *)workDir {
256
+ return !self.repository .gtRepo .isBare ? [self .repository.gtRepo.fileURL.path stringByStandardizingPath ] : nil ;
257
+ }
258
+
259
+ - (void ) _initializeStream {
260
+ if (eventStream) return ;
261
+
262
+ NSMutableArray *array = [NSMutableArray array ];
263
+ if (self.gitDir ) [array addObject: self .gitDir];
264
+ if (self.workDir ) [array addObject: self .workDir];
265
+
266
+ if (!array.count ) return ;
267
+
268
+ FSEventStreamContext gitDirWatcherContext = {0 , (__bridge void *)(self), NULL , NULL , NULL };
269
+ eventStream = FSEventStreamCreate (kCFAllocatorDefault , PBGitRepositoryWatcherCallback, &gitDirWatcherContext,
270
+ (__bridge CFArrayRef)array,
271
+ kFSEventStreamEventIdSinceNow , 1.0 ,
272
+ kFSEventStreamCreateFlagUseCFTypes |
273
+ kFSEventStreamCreateFlagIgnoreSelf |
274
+ kFSEventStreamCreateFlagFileEvents );
275
+ }
276
+
269
277
- (void ) start {
270
278
if (_running)
271
279
return ;
272
280
273
281
// set initial state
274
282
[self gitDirectoryChanged ];
275
283
[self indexChanged ];
276
- ownRef = self; // The callback has no reference to us, so we need to stay alive as long as it may be called
277
- FSEventStreamScheduleWithRunLoop (gitDirEventStream, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
278
- FSEventStreamStart (gitDirEventStream);
284
+ [self _initializeStream ];
279
285
280
- if (workDirEventStream) {
281
- FSEventStreamScheduleWithRunLoop (workDirEventStream, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
282
- FSEventStreamStart (workDirEventStream );
283
- }
286
+ if (!eventStream) return ;
287
+
288
+ FSEventStreamScheduleWithRunLoop (eventStream, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
289
+ FSEventStreamStart (eventStream);
284
290
285
291
_running = YES ;
286
292
}
@@ -289,13 +295,11 @@ - (void) stop {
289
295
if (!_running)
290
296
return ;
291
297
292
- if (workDirEventStream ) {
293
- FSEventStreamStop (workDirEventStream );
294
- FSEventStreamUnscheduleFromRunLoop (workDirEventStream , CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
298
+ if (eventStream ) {
299
+ FSEventStreamStop (eventStream );
300
+ FSEventStreamUnscheduleFromRunLoop (eventStream , CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
295
301
}
296
- FSEventStreamStop (gitDirEventStream);
297
- FSEventStreamUnscheduleFromRunLoop (gitDirEventStream, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
298
- ownRef = nil ; // Now that we can't be called anymore, we can allow ourself to be -dealloc'd
302
+
299
303
_running = NO ;
300
304
}
301
305
0 commit comments