mirror of
https://github.com/kennethreitz-archive/gitx.git
synced 2026-06-05 23:40:18 +00:00
4a8c524692
- 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
380 lines
9.1 KiB
Objective-C
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
|