Source

Murky / Source / HgUncommittedRevision.m

//
//  HgUncommittedRevision.m
//  Murky
//
//  Copyright 2008-2009 Jens Alfke. All rights reserved.
//

#import <CoreServices/CoreServices.h>

#import "HgUncommittedRevision.h"
#import "HgDir.h"
#import "HgRepository.h"
#import "HgOperation.h"

static BOOL moveFileToTrash(HgFile* file)
{
    FSRef f;
    OSStatus os_status = FSPathMakeRef((const UInt8 *)[file.absolutePath fileSystemRepresentation], &f, NULL);
    
    if ( os_status == noErr ) {
        OSStatus tr_status = FSMoveObjectToTrashSync(&f, nil, kFSFileOperationOverwrite);
        if ( tr_status  == noErr ) {
            return YES;
        } else {
            // it was not possible to move the file to trash
            return NO;
        }
    } else {
        // it was not possible to create a path for the file
        return NO;
    }
}


@implementation HgUncommittedRevision

- (id) initWithRepository: (HgRepository*)repo
{
    self = [super initWithRepository: repo localNumber: NSNotFound identifier: kUncommittedRevisionID];
    if( self ) {
    }
    return self;
}

- (BOOL) isUncommitted              {return YES;}
- (BOOL) isHead                     {return NO;}

- (NSString*) localString           {return NSLocalizedString(@"current", 
                                                              @"Revision 'number' displayed for current revision in table");}

- (NSString*) identifierString      {return nil;}

- (BOOL) _getDetails                {return YES;}

- (NSString*) comment
{
    if (_comment.length)
        return _comment;
    else if (_root.isUncommitted)
        return NSLocalizedString(@"--locally modified--", 
                                 @"Default comment for current revision");
    else
        return NSLocalizedString(@"(no changes)", 
                                 @"Comment for unchanged current revision");
}

- (void) setComment: (NSString*)str
{
    _comment = str.copy;
    _shortComment = nil;
}

+ (NSSet*) keyPathsForValuesAffectingComment {
    return [NSSet setWithObject: @"root.isUncommitted"];
}

- (NSString*) shortComment
{
    if (_comment.length)
        return super.shortComment;
    else
        return self.comment;    // special messages
}

- (NSString*) formattedDescription                  {return _comment;}
- (void) setFormattedDescription: (NSString*)str    {self.comment = str;}

- (NSAttributedString*) richFormattedDescription {
    if (_comment)
        return [[NSAttributedString alloc] initWithString: _comment
                                               attributes: $dict({NSFontAttributeName, [NSFont systemFontOfSize: 12.0f]})];
    else
        return nil;
}

- (NSData*) getFileContents: (HgFile*)file error: (NSError**)outError
{
    return [NSData dataWithContentsOfFile: file.absolutePath
                                  options: NSMappedRead
                                    error: outError];
}

- (NSString*) getPathToFileContents: (HgFile*)file inTempDir: (HgTempDir*)tempDir error: (NSError**)outError
{
    if (outError) *outError = nil;
    return file.absolutePath;
}


#pragma mark -
#pragma mark FILES:


- (void) _clearUncleanFiles
{
    if( _uncleanFiles ) {
        [self willChangeValueForKey: @"uncleanFiles"];
        _uncleanFiles = nil;
        [self didChangeValueForKey: @"uncleanFiles"];
    }
}


- (BOOL) updateStatus: (HgDir*)dir error: (NSError**)outError
{
    if( [super updateStatus: dir error: outError] ) {
        [self _clearUncleanFiles];
        return YES;
    } else
        return NO;
}


- (BOOL) updatePaths: (NSSet*)paths error: (NSError**)outError
{
    if (!_root) {
        // If I haven't loaded my files at all, just load 'em and return. Otherwise
        // the code below will end up doing that and then calling -updateStatus again,
        // which is inefficient.
        [self root];
        return YES;
    }
    
    BOOL any = NO;
    for( NSString *path in paths ) {
        HgFile *dir = [self fileAtPath: path];
        if( dir.isDirectory && [super updateStatus: (HgDir*)dir error: outError] )
            any = YES;
    }
    if( any )
        [self _clearUncleanFiles];
    return YES;
}


#pragma mark -
#pragma mark FILE OPERATIONS:


- (NSString*) diffFile: (HgFile*)file withRevisionNumber: (int)otherRev error: (NSError**)outError
{
    if( otherRev==_localNumber )
        return @"";
    // Same as inherited method except only one --rev flag
    [file.repository ignoreNextFileChanges];       // ignore temp file that "hg diff" creates in the repo root dir
    HgOperation *op = [[HgOperation alloc] initWithDirectory: file.root
                                                     command: @"diff",
                       @"--rev", [NSString stringWithFormat: @"%i",otherRev],
                       @"--", file.path, nil];
    if( ! [op run] ) {
        if (outError) *outError = op.error;
        return nil;
    }
    return op.output ?: @"";    
}

- (NSString*) diffFile: (HgFile*)file withRevision: (HgRevision*)other error: (NSError**)outError
{
    if (other==self)
        return @"";
    return [self diffFile: file withRevisionNumber: other.localNumber error: outError];
}


#pragma mark -
#pragma mark ACTIONS:


- (BOOL) applyCommand: (NSString*)command
            arguments: (NSArray*)arguments
              toFiles: (NSArray*)files
                error: (NSError**)outError
{
    NSMutableArray *args = arguments ?[arguments mutableCopy] :[NSMutableArray array];
    if( files ) {
        if( files.count==0 )
            return YES; // no-op
        [args addObject: @"--"];
        for( HgFile *file in files )
            [args addObject: file.path];
    }
    HgOperation *op = [[HgOperation alloc] initWithDirectory: self
                                                     command: command
                                                   arguments: args];
    [_repository ignoreNextFileChanges];
    BOOL result = [op run: outError];
    return result && [self updateStatus: _root error: outError];
}

- (BOOL) addRemoveWithError: (NSError**)outError
{
    return [self applyCommand:@"addremove" arguments:nil toFiles:nil error:outError];
}

- (BOOL) addFiles: (NSArray*)files error: (NSError**)outError
{
    return [self applyCommand: @"add" arguments: nil toFiles: files error: outError];
}

- (BOOL) removeFiles: (NSArray*)files trashModified: (BOOL)trashModified error: (NSError**)outError
{
    Assert(files);
    NSMutableArray *realFiles = [NSMutableArray array], *missingFiles = [NSMutableArray array];
    for( HgFile *file in files ) {
        switch( file.status ) {
            case kNotTracked:
                // Just delete an untracked ('?') file immediately:
                if( ! [[NSFileManager defaultManager] removeItemAtPath: file.absolutePath error: outError] )
                    return NO;
                break;
            case kDeleted:
                [missingFiles addObject: file];
                break;
            case kModified:
                if ( trashModified ) {
                    BOOL trashSuccess = moveFileToTrash(file);
                    if ( trashSuccess ) {
                        [missingFiles addObject: file];
                    } else {
                        // it was not possible to move the file to trash
                        return NO;
                    }
                } else {
                    [realFiles addObject: file];
                }
                break;
            default:
                [realFiles addObject: file];
                break;
        }
    }
    
    if( missingFiles.count )
        if( ! [self applyCommand: @"remove" 
                        arguments: [NSArray arrayWithObject: @"--after"]
                          toFiles: missingFiles
                            error: outError] )
            return NO;
    
    if( realFiles.count )
        if( ! [self applyCommand: @"remove" 
                        arguments: [NSArray arrayWithObject: @"--force"]    // allows modified files to be removed
                          toFiles: realFiles
                            error: outError] )
            return NO;
    return YES;
}

- (BOOL) revertFiles: (NSArray*)files backup: (BOOL)backup error: (NSError**)outError
{
    return [self applyCommand: @"revert"
                    arguments: (backup ?nil :$array(@"--no-backup"))
                      toFiles: files
                        error: outError];
}

- (BOOL) markFiles: (NSArray*)files
          resolved: (BOOL)resolved
             error: (NSError**)outError {
    return [self applyCommand: @"resolve"
                    arguments: $array(resolved ? @"--mark" : @"--unmark")
                      toFiles: files
                        error: outError];
}

- (BOOL) commitFiles: (NSArray*)files
             message: (NSString*)message 
               error: (NSError**)outError
{
    if( message.length==0 )
        return MYMiscError(outError, 
                           @"",
                           NSLocalizedString(@"Missing commit message",
                                             @"Error message for hg commit"));
    if( ! [self applyCommand: @"commit" 
                    arguments: [NSArray arrayWithObjects: @"--message", message, nil]
                      toFiles: files
                        error: outError] )
        return NO;
    
    self.comment = nil;     // I don't represent the revision with that comment anymore...
    return YES;
}


@end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.