1717NSString *kPBGitRepositoryEventTypeUserInfoKey = @" kPBGitRepositoryEventTypeUserInfoKey" ;
1818NSString *kPBGitRepositoryEventPathsUserInfoKey = @" kPBGitRepositoryEventPathsUserInfoKey" ;
1919
20+
2021@interface PBGitRepositoryWatcher ()
2122
2223@property (nonatomic , strong ) NSMutableDictionary *statusCache;
2324
24- - (void ) _handleEventCallback : (NSArray *)eventPaths ;
25+ - (void ) handleGitDirEventCallback : (NSArray *)eventPaths ;
26+ - (void ) handleWorkDirEventCallback : (NSArray *)eventPaths ;
27+
2528@end
2629
2730void PBGitRepositoryWatcherCallback (ConstFSEventStreamRef streamRef,
@@ -30,22 +33,20 @@ void PBGitRepositoryWatcherCallback(ConstFSEventStreamRef streamRef,
3033 void *_eventPaths,
3134 const FSEventStreamEventFlags eventFlags[],
3235 const FSEventStreamEventId eventIds[]){
33- PBGitRepositoryWatcher *watcher = (__bridge PBGitRepositoryWatcher*)clientCallBackInfo;
36+ PBGitRepositoryWatcherCallbackBlock block = (__bridge PBGitRepositoryWatcherCallbackBlock)clientCallBackInfo;
37+
3438 NSMutableArray *changePaths = [[NSMutableArray alloc ] init ];
3539 NSArray *eventPaths = (__bridge NSArray *)_eventPaths;
3640 for (int i = 0 ; i < numEvents; ++i) {
3741 NSString *path = [eventPaths objectAtIndex: i];
38- if ([path hasSuffix: @" .lock" ]) {
39- continue ;
40- }
4142 PBGitRepositoryWatcherEventPath *ep = [[PBGitRepositoryWatcherEventPath alloc ] init ];
42- ep.path = path;
43+ ep.path = [ path stringByStandardizingPath ] ;
4344 ep.flag = eventFlags[i];
4445 [changePaths addObject: ep];
45-
46+
4647 }
47- if (changePaths.count ) {
48- [watcher _handleEventCallback: changePaths] ;
48+ if (block && changePaths.count ) {
49+ block ( changePaths) ;
4950 }
5051}
5152
@@ -55,39 +56,79 @@ @implementation PBGitRepositoryWatcher
5556
5657- (id ) initWithRepository : (PBGitRepository *)theRepository {
5758 self = [super init ];
58- if (!self)
59+ if (!self) {
5960 return nil ;
60-
61+ }
62+
63+ __weak PBGitRepositoryWatcher* weakSelf = self;
6164 repository = theRepository;
62- FSEventStreamContext context = {0 , (__bridge void *)(self), NULL , NULL , NULL };
6365
64- NSString *indexPath = repository.gtRepo .gitDirectoryURL .path ;
65- if (!indexPath) {
66- return nil ;
66+ {
67+ gitDir = [repository.gtRepo.gitDirectoryURL.path stringByStandardizingPath ];
68+ if (!gitDir) {
69+ return nil ;
70+ }
71+ gitDirChangedBlock = ^(NSArray *changeEvents){
72+ NSMutableArray *filteredEvents = [NSMutableArray new ];
73+ for (PBGitRepositoryWatcherEventPath *event in changeEvents) {
74+ // exclude all changes to .lock files
75+ if ([event.path hasSuffix: @" .lock" ]) {
76+ continue ;
77+ }
78+ [filteredEvents addObject: event];
79+ }
80+ if (filteredEvents.count ) {
81+ [weakSelf handleGitDirEventCallback: filteredEvents];
82+ }
83+ };
84+ FSEventStreamContext gitDirWatcherContext = {0 , (__bridge void *)(gitDirChangedBlock), NULL , NULL , NULL };
85+ gitDirEventStream = FSEventStreamCreate (kCFAllocatorDefault , PBGitRepositoryWatcherCallback, &gitDirWatcherContext,
86+ (__bridge CFArrayRef)@[gitDir],
87+ kFSEventStreamEventIdSinceNow , 1.0 ,
88+ kFSEventStreamCreateFlagUseCFTypes |
89+ kFSEventStreamCreateFlagIgnoreSelf |
90+ kFSEventStreamCreateFlagFileEvents );
91+
6792 }
68- NSString *workDir = repository.gtRepo .isBare ? nil : repository.gtRepo .fileURL .path ;
69- NSArray *paths = nil ;
70- if (workDir) {
71- paths = @[indexPath, workDir];
72- } else {
73- paths = @[indexPath];
93+ {
94+ workDir = repository.gtRepo .isBare ? nil : [repository.gtRepo.fileURL.path stringByStandardizingPath ];
95+ if (workDir) {
96+ workDirChangedBlock = ^(NSArray *changeEvents){
97+ NSMutableArray *filteredEvents = [NSMutableArray new ];
98+ PBGitRepositoryWatcher *watcher = weakSelf;
99+ if (!watcher) {
100+ return ;
101+ }
102+ for (PBGitRepositoryWatcherEventPath *event in changeEvents) {
103+ // exclude anything under the .git dir
104+ if ([event.path hasPrefix: watcher->gitDir]) {
105+ continue ;
106+ }
107+ [filteredEvents addObject: event];
108+ }
109+ if (filteredEvents.count ) {
110+ [watcher handleWorkDirEventCallback: filteredEvents];
111+ }
112+ };
113+ FSEventStreamContext workDirWatcherContext = {0 , (__bridge void *)(workDirChangedBlock), NULL , NULL , NULL };
114+ workDirEventStream = FSEventStreamCreate (kCFAllocatorDefault , PBGitRepositoryWatcherCallback, &workDirWatcherContext,
115+ (__bridge CFArrayRef)@[workDir],
116+ kFSEventStreamEventIdSinceNow , 1.0 ,
117+ kFSEventStreamCreateFlagUseCFTypes |
118+ kFSEventStreamCreateFlagIgnoreSelf |
119+ kFSEventStreamCreateFlagFileEvents );
120+ }
74121 }
75122
76- self.statusCache = [NSMutableDictionary new ];
77123
78- // Create and activate event stream
79- eventStream = FSEventStreamCreate (kCFAllocatorDefault , PBGitRepositoryWatcherCallback, &context,
80- (__bridge CFArrayRef)paths,
81- kFSEventStreamEventIdSinceNow , 1.0 ,
82- kFSEventStreamCreateFlagUseCFTypes |
83- kFSEventStreamCreateFlagIgnoreSelf |
84- kFSEventStreamCreateFlagFileEvents );
124+ self.statusCache = [NSMutableDictionary new ];
125+
85126 if ([PBGitDefaults useRepositoryWatcher ])
86127 [self start ];
87128 return self;
88129}
89130
90- - (NSDate *) _fileModificationDateAtPath : (NSString *)path {
131+ - (NSDate *) fileModificationDateAtPath : (NSString *)path {
91132 NSError * error;
92133 NSDictionary *attrs = [[NSFileManager defaultManager ] attributesOfItemAtPath: path
93134 error: &error];
@@ -99,11 +140,12 @@ - (NSDate *) _fileModificationDateAtPath:(NSString *)path {
99140 return [attrs objectForKey: NSFileModificationDate ];
100141}
101142
102- - (BOOL ) _indexChanged {
143+ - (BOOL ) indexChanged {
103144 if (self.repository .isBareRepository ) {
104145 return NO ;
105146 }
106- NSDate *newTouchDate = [self _fileModificationDateAtPath: [repository.gitURL.path stringByAppendingPathComponent: @" index" ]];
147+
148+ NSDate *newTouchDate = [self fileModificationDateAtPath: [gitDir stringByAppendingPathComponent: @" index" ]];
107149 if (![newTouchDate isEqual: indexTouchDate]) {
108150 indexTouchDate = newTouchDate;
109151 return YES ;
@@ -112,7 +154,7 @@ - (BOOL) _indexChanged {
112154 return NO ;
113155}
114156
115- - (BOOL ) _gitDirectoryChanged {
157+ - (BOOL ) gitDirectoryChanged {
116158
117159 for (NSURL * fileURL in [[NSFileManager defaultManager ] contentsOfDirectoryAtURL: repository.gitURL
118160 includingPropertiesForKeys: [NSArray arrayWithObject: NSURLContentModificationDateKey ]
@@ -140,79 +182,88 @@ - (BOOL) _gitDirectoryChanged {
140182 return NO ;
141183}
142184
143- - (void ) _handleEventCallback : (NSArray *)eventPaths {
185+ - (void ) handleGitDirEventCallback : (NSArray *)eventPaths
186+ {
144187 PBGitRepositoryWatcherEventType event = 0x0 ;
145188
146- if ([self _indexChanged ])
147- {
148- // NSLog(@"Watcher found an index change");
189+ if ([self indexChanged ]) {
149190 event |= PBGitRepositoryWatcherEventTypeIndex;
150191 }
151-
152- NSString * ourRepo_ns = repository.gitURL .path ;
153- // libgit2 API results for directories end with a '/'
154- if (![ourRepo_ns hasSuffix: @" /" ])
155- ourRepo_ns = [NSString stringWithFormat: @" %@ /" , ourRepo_ns];
156-
192+
193+
157194 NSMutableArray *paths = [NSMutableArray array ];
158-
159195 for (PBGitRepositoryWatcherEventPath *eventPath in eventPaths) {
160196 // .git dir
161- if ([[ eventPath.path stringByStandardizingPath ] isEqual: [repository.gitURL.path stringByStandardizingPath ] ]) {
162- if ([self _gitDirectoryChanged ] || eventPath.flag != kFSEventStreamEventFlagNone ) {
197+ if ([eventPath.path isEqualToString: gitDir ]) {
198+ if ([self gitDirectoryChanged ] || eventPath.flag != kFSEventStreamEventFlagNone ) {
163199 event |= PBGitRepositoryWatcherEventTypeGitDirectory;
164200 [paths addObject: eventPath.path];
165- // NSLog(@"Watcher: git dir change in %@", eventPath.path);
166201 }
167202 }
168-
203+ // ignore objects dir ... ?
204+ else if ([eventPath.path rangeOfString: [gitDir stringByAppendingPathComponent: @" objects" ]].location != NSNotFound ) {
205+ continue ;
206+ }
207+ // index is already covered
208+ else if ([eventPath.path rangeOfString: [gitDir stringByAppendingPathComponent: @" index" ]].location != NSNotFound ) {
209+ continue ;
210+ }
169211 // subdirs of .git dir
170- else if ([eventPath.path rangeOfString: repository.gitURL.path].location != NSNotFound ) {
171- // ignore changes to lock files
172- if ([eventPath.path hasSuffix: @" .lock" ])
173- {
174- // NSLog(@"Watcher: ignoring change to lock file: %@", eventPath.path);
175- continue ;
176- }
212+ else if ([eventPath.path rangeOfString: gitDir].location != NSNotFound ) {
177213 event |= PBGitRepositoryWatcherEventTypeGitDirectory;
178214 [paths addObject: eventPath.path];
179- // NSLog(@"Watcher: git dir subdir change in %@", eventPath.path);
180215 }
216+ }
217+
218+ if (event != 0x0 ){
219+ NSDictionary *eventInfo = @{kPBGitRepositoryEventTypeUserInfoKey :@(event),
220+ kPBGitRepositoryEventPathsUserInfoKey :paths};
181221
182- else {
183- unsigned int fileStatus = 0 ;
184- NSString *repoPrefix = self.repository .fileURL .path ;
185- // TODO: fix exception
186- NSString *eventRepoRelativePath = [eventPath.path substringFromIndex: (repoPrefix.length + 1 )];
187- int gitError = git_status_file (&fileStatus, self.repository .gtRepo .git_repository , eventRepoRelativePath.UTF8String );
188- if (gitError == GIT_OK) {
189- if (fileStatus & GIT_STATUS_IGNORED) {
190- // NSLog(@"ignoring change to ignored file: %@", eventPath.path);
191- } else {
192- NSNumber *oldStatus = self.statusCache [eventPath.path];
193- NSNumber *newStatus = @(fileStatus);
194- if (![oldStatus isEqualTo: newStatus]) {
195- // NSLog(@"file changed status: %@", eventPath.path);
196- [paths addObject: eventPath.path];
197- event |= PBGitRepositoryWatcherEventTypeWorkingDirectory;
198- } else if (fileStatus & GIT_STATUS_WT_MODIFIED) {
199- // NSLog(@"modified file touched: %@", eventPath.path);
200- event |= PBGitRepositoryWatcherEventTypeWorkingDirectory;
201- [paths addObject: eventPath.path];
202- }
203- self.statusCache [eventPath.path] = newStatus;
222+ [[NSNotificationCenter defaultCenter ] postNotificationName: PBGitRepositoryEventNotification object: repository userInfo: eventInfo];
223+ }
224+ }
225+
226+ - (void )handleWorkDirEventCallback : (NSArray *)eventPaths
227+ {
228+ PBGitRepositoryWatcherEventType event = 0x0 ;
229+
230+ NSMutableArray *paths = [NSMutableArray array ];
231+ for (PBGitRepositoryWatcherEventPath *eventPath in eventPaths) {
232+ unsigned int fileStatus = 0 ;
233+ if (![eventPath.path hasPrefix: workDir]) {
234+ continue ;
235+ }
236+ if ([eventPath.path isEqualToString: workDir]) {
237+ event |= PBGitRepositoryWatcherEventTypeWorkingDirectory;
238+ [paths addObject: eventPath.path];
239+ continue ;
240+ }
241+ NSString *eventRepoRelativePath = [eventPath.path substringFromIndex: (workDir.length + 1 )];
242+ int gitError = git_status_file (&fileStatus, self.repository .gtRepo .git_repository , eventRepoRelativePath.UTF8String );
243+ if (gitError == GIT_OK) {
244+ if (fileStatus & GIT_STATUS_IGNORED) {
245+ // NSLog(@"ignoring change to ignored file: %@", eventPath.path);
246+ } else {
247+ NSNumber *oldStatus = self.statusCache [eventPath.path];
248+ NSNumber *newStatus = @(fileStatus);
249+ if (![oldStatus isEqualTo: newStatus]) {
250+ // NSLog(@"file changed status: %@", eventPath.path);
251+ [paths addObject: eventPath.path];
252+ event |= PBGitRepositoryWatcherEventTypeWorkingDirectory;
253+ } else if (fileStatus & GIT_STATUS_WT_MODIFIED) {
254+ // NSLog(@"modified file touched: %@", eventPath.path);
255+ event |= PBGitRepositoryWatcherEventTypeWorkingDirectory;
256+ [paths addObject: eventPath.path];
204257 }
258+ self.statusCache [eventPath.path] = newStatus;
205259 }
206260 }
207261 }
208-
262+
209263 if (event != 0x0 ){
210- // NSLog(@"PBGitRepositoryWatcher firing notification for repository %@ with flag %lu", repository, event);
211- NSDictionary *eventInfo = [NSDictionary dictionaryWithObjectsAndKeys:
212- [NSNumber numberWithUnsignedInt: event], kPBGitRepositoryEventTypeUserInfoKey ,
213- paths, kPBGitRepositoryEventPathsUserInfoKey ,
214- NULL ];
215-
264+ NSDictionary *eventInfo = @{kPBGitRepositoryEventTypeUserInfoKey :@(event),
265+ kPBGitRepositoryEventPathsUserInfoKey :paths};
266+
216267 [[NSNotificationCenter defaultCenter ] postNotificationName: PBGitRepositoryEventNotification object: repository userInfo: eventInfo];
217268 }
218269}
@@ -222,20 +273,30 @@ - (void) start {
222273 return ;
223274
224275 // set initial state
225- [self _gitDirectoryChanged ];
226- [self _indexChanged ];
276+ [self gitDirectoryChanged ];
277+ [self indexChanged ];
227278 ownRef = self; // The callback has no reference to us, so we need to stay alive as long as it may be called
228- FSEventStreamScheduleWithRunLoop (eventStream, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
229- FSEventStreamStart (eventStream);
279+ FSEventStreamScheduleWithRunLoop (gitDirEventStream, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
280+ FSEventStreamStart (gitDirEventStream);
281+
282+ if (workDirEventStream) {
283+ FSEventStreamScheduleWithRunLoop (workDirEventStream, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
284+ FSEventStreamStart (workDirEventStream);
285+ }
286+
230287 _running = YES ;
231288}
232289
233290- (void ) stop {
234291 if (!_running)
235292 return ;
236293
237- FSEventStreamStop (eventStream);
238- FSEventStreamUnscheduleFromRunLoop (eventStream, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
294+ if (workDirEventStream) {
295+ FSEventStreamStop (workDirEventStream);
296+ FSEventStreamUnscheduleFromRunLoop (workDirEventStream, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
297+ }
298+ FSEventStreamStop (gitDirEventStream);
299+ FSEventStreamUnscheduleFromRunLoop (gitDirEventStream, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode );
239300 ownRef = nil ; // Now that we can't be called anymore, we can allow ourself to be -dealloc'd
240301 _running = NO ;
241302}
0 commit comments