Files
gitx/PBGitHistoryList.m
T
Nathan Kinsinger 4a8c524692 Add branch view filters to history scope bar
- filters for All, Local/Remote, and the selected branch
        - "Local" includes both branches and tags
        - "Remote" includes all branches from the same remote as the selected remote branch (i.e. not other remotes)

Changes to make the above work:
    - add a history list class between the repository and rev list
        - store a project rev list with all the commits from the project
        - use the project rev list to graph the history for individual branches when there have been no changes
        - use a different rev list to show non-simple revs (history of a file, revs from the gitx tool)
        - update the commits in chunks to a mutable array so the table view's array controller has less work to do
        - only update the project rev list from git when actually necessary
    - don't add the All Branches and Local Branches revs to the branches array
    - some changes related to forcing the project's rev list to update when changes are made
    - some changes related to not causing updates too often
    - store the selected filter in user defaults
    - when the graphing is done select the commit for the branch
2010-03-13 22:16:44 -07:00

380 lines
9.1 KiB
Objective-C

//
// PBGitHistoryList.m
// GitX
//
// Created by Nathan Kinsinger on 2/20/10.
// Copyright 2010 Nathan Kinsinger. All rights reserved.
//
#import "PBGitHistoryList.h"
#import "PBGitRepository.h"
#import "PBGitRevList.h"
#import "PBGitGrapher.h"
#import "PBGitHistoryGrapher.h"
@interface PBGitHistoryList ()
- (void) resetGraphing;
- (PBGitHistoryGrapher *) grapher;
- (NSInvocationOperation *) operationForCommits:(NSArray *)newCommits;
- (void) updateProjectHistoryForRev:(PBGitRevSpecifier *)rev;
- (void) updateHistoryForRev:(PBGitRevSpecifier *)rev;
@end
@implementation PBGitHistoryList
@synthesize projectRevList;
@synthesize commits;
@synthesize isUpdating;
@synthesize updatedGraph;
@dynamic projectCommits;
#pragma mark -
#pragma mark Public
- (id) initWithRepository:(PBGitRepository *)repo
{
commits = [NSMutableArray array];
repository = repo;
lastBranchFilter = -1;
[repository addObserver:self forKeyPath:@"currentBranch" options:0 context:@"currentBranch"];
[repository addObserver:self forKeyPath:@"currentBranchFilter" options:0 context:@"currentBranch"];
[repository addObserver:self forKeyPath:@"hasChanged" options:0 context:@"repositoryHasChanged"];
shouldReloadProjectHistory = YES;
projectRevList = [[PBGitRevList alloc] initWithRepository:repository rev:[PBGitRevSpecifier allBranchesRevSpec] shouldGraph:NO];
return self;
}
- (void) forceUpdate
{
if ([repository.currentBranch isSimpleRef])
shouldReloadProjectHistory = YES;
[self updateHistory];
}
- (void) updateHistory
{
PBGitRevSpecifier *rev = repository.currentBranch;
if (!rev)
return;
if ([rev isSimpleRef])
[self updateProjectHistoryForRev:rev];
else
[self updateHistoryForRev:rev];
}
- (NSArray *) projectCommits
{
return [projectRevList.commits copy];
}
#pragma mark -
#pragma mark History Grapher delegate methods
- (void) addCommitsFromArray:(NSArray *)array
{
if (!array || [array count] == 0)
return;
if (resetCommits) {
self.commits = [NSMutableArray array];
resetCommits = NO;
}
NSRange range = NSMakeRange([commits count], [array count]);
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:range];
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@"commits"];
[commits addObjectsFromArray:array];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@"commits"];
}
- (void) finishedGraphing
{
if (!currentRevList.isParsing && ([[graphQueue operations] count] == 0)) {
self.isUpdating = NO;
[self performSelector:@selector(setUpdatedGraph:) withObject:[NSDate date] afterDelay:0];
}
}
#pragma mark -
#pragma mark Private
- (void) resetGraphing
{
resetCommits = YES;
self.isUpdating = YES;
[graphQueue setSuspended:YES];
if (graphQueue)
[graphQueue removeObserver:self forKeyPath:@"operations"];
graphQueue = [[NSOperationQueue alloc] init];
[graphQueue addObserver:self forKeyPath:@"operations" options:0 context:@"operations"];
lastOperation = nil;
grapher = [self grapher];
}
- (NSInvocationOperation *) operationForCommits:(NSArray *)newCommits
{
NSInvocationOperation *graphOperation = [[NSInvocationOperation alloc] initWithTarget:grapher selector:@selector(graphCommits:) object:newCommits];
if (lastOperation)
[graphOperation addDependency:lastOperation];
lastOperation = graphOperation;
return graphOperation;
}
- (NSSet *) baseCommitsForLocalRefs
{
NSMutableSet *baseCommitSHAs = [NSMutableSet set];
NSDictionary *refs = repository.refs;
for (NSString *sha in refs)
for (PBGitRef *ref in [refs objectForKey:sha])
if ([ref isBranch] || [ref isTag])
[baseCommitSHAs addObject:sha];
return baseCommitSHAs;
}
- (NSSet *) baseCommitsForRemoteRefs
{
NSMutableSet *baseCommitSHAs = [NSMutableSet set];
NSDictionary *refs = repository.refs;
PBGitRef *remoteRef = [[repository.currentBranch ref] remoteRef];
for (NSString *sha in refs)
for (PBGitRef *ref in [refs objectForKey:sha])
if ([remoteRef isEqualToRef:[ref remoteRef]])
[baseCommitSHAs addObject:sha];
return baseCommitSHAs;
}
- (NSSet *) baseCommits
{
if ((repository.currentBranchFilter == kGitXSelectedBranchFilter) || (repository.currentBranchFilter == kGitXAllBranchesFilter)) {
if (lastSHA)
return [NSMutableSet setWithObject:lastSHA];
else if ([repository.currentBranch isSimpleRef]) {
PBGitRef *currentRef = [repository.currentBranch ref];
NSString *sha = [repository shaForRef:currentRef];
if (sha)
return [NSMutableSet setWithObject:sha];
}
}
else if (repository.currentBranchFilter == kGitXLocalRemoteBranchesFilter) {
if ([[repository.currentBranch ref] isRemote])
return [self baseCommitsForRemoteRefs];
else
return [self baseCommitsForLocalRefs];
}
return [NSMutableSet set];
}
- (PBGitHistoryGrapher *) grapher
{
BOOL viewAllBranches = (repository.currentBranchFilter == kGitXAllBranchesFilter);
return [[PBGitHistoryGrapher alloc] initWithBaseCommits:[self baseCommits] viewAllBranches:viewAllBranches delegate:self];
}
- (void) setCurrentRevList:(PBGitRevList *)parser
{
if (currentRevList == parser)
return;
if (currentRevList) {
[currentRevList removeObserver:self forKeyPath:@"commits"];
[currentRevList removeObserver:self forKeyPath:@"isParsing"];
}
currentRevList = parser;
[currentRevList addObserver:self forKeyPath:@"commits" options:NSKeyValueObservingOptionNew context:@"commitsUpdated"];
[currentRevList addObserver:self forKeyPath:@"isParsing" options:0 context:@"revListParsing"];
}
- (BOOL) isAllBranchesOnlyUpdate
{
return (lastBranchFilter == kGitXAllBranchesFilter) && (repository.currentBranchFilter == kGitXAllBranchesFilter);
}
- (BOOL) isLocalRemoteOnlyUpdate:(PBGitRevSpecifier *)rev
{
if ((lastBranchFilter == kGitXLocalRemoteBranchesFilter) && (repository.currentBranchFilter == kGitXLocalRemoteBranchesFilter)) {
if (!lastRemoteRef && ![[rev ref] isRemote])
return YES;
if ([lastRemoteRef isEqualToRef:[[rev ref] remoteRef]])
return YES;
}
return NO;
}
- (BOOL) selectedBranchNeedsNewGraph:(PBGitRevSpecifier *)rev
{
if (![rev isSimpleRef])
return YES;
if ([self isAllBranchesOnlyUpdate] || [self isLocalRemoteOnlyUpdate:rev]) {
lastRemoteRef = [[rev ref] remoteRef];
lastSHA = nil;
self.isUpdating = NO;
self.updatedGraph = [NSDate date];
return NO;
}
NSString *revSHA = [repository shaForRef:[rev ref]];
if ([revSHA isEqualToString:lastSHA] && (lastBranchFilter == repository.currentBranchFilter))
return NO;
lastBranchFilter = repository.currentBranchFilter;
lastRemoteRef = [[rev ref] remoteRef];
lastSHA = revSHA;
return YES;
}
- (BOOL) haveRefsBeenModified
{
[repository reloadRefs];
NSMutableSet *currentRefSHAs = [NSMutableSet setWithArray:[repository.refs allKeys]];
[currentRefSHAs minusSet:lastRefSHAs];
lastRefSHAs = [NSSet setWithArray:[repository.refs allKeys]];
return [currentRefSHAs count] != 0;
}
#pragma mark updating history
- (void) updateProjectHistoryForRev:(PBGitRevSpecifier *)rev
{
[self setCurrentRevList:projectRevList];
if ([self haveRefsBeenModified])
shouldReloadProjectHistory = YES;
if (![self selectedBranchNeedsNewGraph:rev] && !shouldReloadProjectHistory)
return;
[self resetGraphing];
if (shouldReloadProjectHistory) {
shouldReloadProjectHistory = NO;
lastBranchFilter = -1;
lastRemoteRef = nil;
lastSHA = nil;
[projectRevList loadRevisons];
return;
}
[graphQueue addOperation:[self operationForCommits:projectRevList.commits]];
}
- (void) updateHistoryForRev:(PBGitRevSpecifier *)rev
{
PBGitRevList *otherRevListParser = [[PBGitRevList alloc] initWithRepository:repository rev:rev shouldGraph:YES];
[self setCurrentRevList:otherRevListParser];
[self resetGraphing];
lastBranchFilter = -1;
lastRemoteRef = nil;
lastSHA = nil;
[otherRevListParser loadRevisons];
}
#pragma mark -
#pragma mark Key Value Observing
- (void) removeObservers
{
[repository removeObserver:self forKeyPath:@"currentBranch"];
[repository removeObserver:self forKeyPath:@"hasChanged"];
if (currentRevList) {
[currentRevList removeObserver:self forKeyPath:@"commits"];
[currentRevList removeObserver:self forKeyPath:@"isParsing"];
}
if (graphQueue)
[graphQueue removeObserver:self forKeyPath:@"operations"];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([@"currentBranch" isEqualToString:context]) {
[self updateHistory];
return;
}
if ([@"repositoryHasChanged" isEqualToString:context]) {
[self forceUpdate];
return;
}
if ([@"commitsUpdated" isEqualToString:context]) {
NSInteger changeKind = [(NSNumber *)[change objectForKey:NSKeyValueChangeKindKey] intValue];
if (changeKind == NSKeyValueChangeInsertion) {
NSArray *newCommits = [change objectForKey:NSKeyValueChangeNewKey];
if ([repository.currentBranch isSimpleRef])
[graphQueue addOperation:[self operationForCommits:newCommits]];
else
[self addCommitsFromArray:newCommits];
}
return;
}
if ([@"revListParsing" isEqualToString:context] || [@"operations" isEqualToString:context]) {
[self finishedGraphing];
return;
}
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
@end