├── LICENSE ├── NSDataCompression.h ├── NSDataCompression.m ├── ObjGit.h ├── ObjGit.m ├── ObjGitCommit.h ├── ObjGitCommit.m ├── ObjGitObject.h ├── ObjGitObject.m ├── ObjGitServerHandler.h ├── ObjGitServerHandler.m ├── ObjGitTree.h ├── ObjGitTree.m └── README /LICENSE: -------------------------------------------------------------------------------- 1 | I'm trying to figure out the license right now. Currently, some of the code is from a GPL project, 2 | so until I get permission, this has to be GPLd, but eventually it will have to be MIT or LGPL. If 3 | neccesary, I will rewrite the borrowed code to make that happen. 4 | 5 | Current (temporary) license: 6 | http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt 7 | -------------------------------------------------------------------------------- /NSDataCompression.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDataCompression.h 3 | // ObjGit 4 | // 5 | // thankfully borrowed from the Etoile framework 6 | // 7 | 8 | #include 9 | 10 | @interface NSData (Compression) 11 | 12 | - (NSData *) compressedData; 13 | - (NSData *) decompressedData; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NSDataCompression.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDataCompression.m 3 | // ObjGit 4 | // 5 | // thankfully borrowed from the Etoile framework 6 | // 7 | 8 | #import "NSDataCompression.h" 9 | #include 10 | 11 | @implementation NSData (Compression) 12 | - (NSData *) compressedData 13 | { 14 | unsigned int srcLength = [self length]; 15 | if (srcLength > 0) 16 | { 17 | uLong buffLength = srcLength * 1.001 + 12; 18 | NSMutableData *compData = [[NSMutableData alloc] initWithCapacity:buffLength]; 19 | [compData increaseLengthBy:buffLength]; 20 | int error = compress( [compData mutableBytes], &buffLength, 21 | [self bytes], srcLength ); 22 | switch( error ) { 23 | case Z_OK: 24 | [compData setLength: buffLength]; 25 | return [NSData dataWithBytes:[compData bytes] length:buffLength]; 26 | break; 27 | default: 28 | NSAssert( YES, @"Error compressing: Memory Error!" ); 29 | break; 30 | } 31 | //[compData release]; 32 | } 33 | return nil; 34 | } 35 | 36 | - (NSData *) decompressedData 37 | { 38 | if ([self length] == 0) return self; 39 | 40 | unsigned full_length = [self length]; 41 | unsigned half_length = [self length] / 2; 42 | 43 | NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length]; 44 | BOOL done = NO; 45 | int status; 46 | 47 | z_stream strm; 48 | strm.next_in = (Bytef *)[self bytes]; 49 | strm.avail_in = [self length]; 50 | strm.total_out = 0; 51 | strm.zalloc = Z_NULL; 52 | strm.zfree = Z_NULL; 53 | 54 | if (inflateInit (&strm) != Z_OK) return nil; 55 | while (!done) 56 | { 57 | // Make sure we have enough room and reset the lengths. 58 | if (strm.total_out >= [decompressed length]) 59 | [decompressed increaseLengthBy: half_length]; 60 | strm.next_out = [decompressed mutableBytes] + strm.total_out; 61 | strm.avail_out = [decompressed length] - strm.total_out; 62 | 63 | // Inflate another chunk. 64 | status = inflate (&strm, Z_SYNC_FLUSH); 65 | if (status == Z_STREAM_END) done = YES; 66 | else if (status != Z_OK) break; 67 | } 68 | if (inflateEnd (&strm) != Z_OK) return nil; 69 | 70 | // Set real length. 71 | if (done) 72 | { 73 | [decompressed setLength: strm.total_out]; 74 | return [NSData dataWithData: decompressed]; 75 | } 76 | else 77 | return nil; 78 | 79 | } 80 | @end 81 | -------------------------------------------------------------------------------- /ObjGit.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObjGit.h 3 | // ObjGit 4 | // 5 | 6 | //#import 7 | //#import 8 | //#include 9 | 10 | #import 11 | #import "ObjGitObject.h" 12 | 13 | @interface ObjGit : NSObject { 14 | NSString* gitDirectory; 15 | NSString* gitName; 16 | } 17 | 18 | @property(copy, readwrite) NSString *gitDirectory; 19 | @property(copy, readwrite) NSString *gitName; 20 | 21 | - (BOOL) openRepo:(NSString *)dirPath; 22 | - (BOOL) ensureGitPath; 23 | - (void) initGitRepo; 24 | - (NSArray *) getAllRefs; 25 | 26 | - (NSString *) writeObject:(NSData *)objectData withType:(NSString *)type withSize:(int)size; 27 | - (void) updateRef:(NSString *)refName toSha:(NSString *)toSha; 28 | 29 | - (NSMutableArray *) getCommitsFromSha:(NSString *)shaValue withLimit:(int)commitSize; 30 | - (NSString *) getLooseObjectPathBySha:(NSString *)shaValue; 31 | - (BOOL) hasObject: (NSString *)sha1; 32 | - (ObjGitObject *) getObjectFromSha:(NSString *)sha1; 33 | 34 | + (int) isAlpha:(unsigned char)n ; 35 | + (int) gitUnpackHex:(const unsigned char *)rawsha fillSha:(char *)sha1; 36 | + (int) gitPackHex:(const char *)sha1 fillRawSha:(unsigned char *)rawsha; 37 | 38 | @end -------------------------------------------------------------------------------- /ObjGit.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObjGit.m 3 | // ObjGit 4 | // 5 | 6 | #import "ObjGit.h" 7 | #import "ObjGitObject.h" 8 | #import "ObjGitCommit.h" 9 | #import "ObjGitServerHandler.h" 10 | #import "NSDataCompression.h" 11 | 12 | #include 13 | 14 | @implementation ObjGit 15 | 16 | @synthesize gitDirectory; 17 | @synthesize gitName; 18 | 19 | - (id) init 20 | { 21 | return self; 22 | } 23 | 24 | - (void) dealloc 25 | { 26 | [super dealloc]; 27 | } 28 | 29 | - (BOOL) ensureGitPath { 30 | BOOL isDir; 31 | NSFileManager *fm = [NSFileManager defaultManager]; 32 | if ([fm fileExistsAtPath:self.gitDirectory isDirectory:&isDir] && isDir) { 33 | return YES; 34 | } else { 35 | [self initGitRepo]; 36 | } 37 | return YES; 38 | } 39 | 40 | - (NSArray *) getAllRefs 41 | { 42 | BOOL isDir=NO; 43 | NSMutableArray *refsFinal = [[NSMutableArray alloc] init]; 44 | NSString *tempRef, *thisSha; 45 | NSString *refsPath = [self.gitDirectory stringByAppendingPathComponent:@"refs"]; 46 | NSFileManager *fileManager = [NSFileManager defaultManager]; 47 | if ([fileManager fileExistsAtPath:refsPath isDirectory:&isDir] && isDir) { 48 | NSEnumerator *e = [[fileManager subpathsAtPath:refsPath] objectEnumerator]; 49 | NSString *thisRef; 50 | while ( (thisRef = [e nextObject]) ) { 51 | tempRef = [refsPath stringByAppendingPathComponent:thisRef]; 52 | thisRef = [@"refs" stringByAppendingPathComponent:thisRef]; 53 | 54 | if ([fileManager fileExistsAtPath:tempRef isDirectory:&isDir] && !isDir) { 55 | thisSha = [NSString stringWithContentsOfFile:tempRef encoding:NSASCIIStringEncoding error:nil]; 56 | [refsFinal addObject:[NSArray arrayWithObjects:thisRef,thisSha,nil]]; 57 | if([thisRef isEqualToString:@"refs/heads/master"]) 58 | [refsFinal addObject:[NSArray arrayWithObjects:@"HEAD",thisSha,nil]]; 59 | } 60 | } 61 | } 62 | return refsFinal; 63 | } 64 | 65 | - (void) updateRef:(NSString *)refName toSha:(NSString *)toSha 66 | { 67 | NSFileManager *fm = [NSFileManager defaultManager]; 68 | NSString *refPath = [self.gitDirectory stringByAppendingPathComponent:refName]; 69 | [fm createFileAtPath:refPath contents:[NSData dataWithBytes:[toSha UTF8String] length:[toSha length]] attributes:nil]; 70 | } 71 | 72 | - (void) initGitRepo { 73 | NSFileManager *fm = [NSFileManager defaultManager]; 74 | [fm createDirectoryAtPath:self.gitDirectory attributes:nil]; 75 | 76 | //NSLog(@"Dir Created: %@ %d", gitDirectory, [gitDirectory length]); 77 | NSString *config = @"[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = true\n\tlogallrefupdates = true\n"; 78 | NSString *configFile = [self.gitDirectory stringByAppendingPathComponent:@"config"]; 79 | [fm createFileAtPath:configFile contents:[NSData dataWithBytes:[config UTF8String] length:[config length]] attributes:nil]; 80 | 81 | NSString *head = @"ref: refs/heads/master\n"; 82 | NSString *headFile = [self.gitDirectory stringByAppendingPathComponent:@"HEAD"]; 83 | [fm createFileAtPath:headFile contents:[NSData dataWithBytes:[head UTF8String] length:[head length]] attributes:nil]; 84 | 85 | [fm createDirectoryAtPath:[self.gitDirectory stringByAppendingPathComponent:@"refs"] attributes:nil]; 86 | [fm createDirectoryAtPath:[self.gitDirectory stringByAppendingPathComponent:@"refs/heads"] attributes:nil]; 87 | [fm createDirectoryAtPath:[self.gitDirectory stringByAppendingPathComponent:@"refs/tags"] attributes:nil]; 88 | [fm createDirectoryAtPath:[self.gitDirectory stringByAppendingPathComponent:@"objects"] attributes:nil]; 89 | [fm createDirectoryAtPath:[self.gitDirectory stringByAppendingPathComponent:@"objects/info"] attributes:nil]; 90 | [fm createDirectoryAtPath:[self.gitDirectory stringByAppendingPathComponent:@"objects/pack"] attributes:nil]; 91 | [fm createDirectoryAtPath:[self.gitDirectory stringByAppendingPathComponent:@"branches"] attributes:nil]; 92 | [fm createDirectoryAtPath:[self.gitDirectory stringByAppendingPathComponent:@"hooks"] attributes:nil]; 93 | [fm createDirectoryAtPath:[self.gitDirectory stringByAppendingPathComponent:@"info"] attributes:nil]; 94 | } 95 | 96 | - (NSString *) writeObject:(NSData *)objectData withType:(NSString *)type withSize:(int)size 97 | { 98 | NSMutableData *object; 99 | NSString *header, *path, *shaStr; 100 | unsigned char rawsha[20]; 101 | char sha1[41]; 102 | 103 | header = [NSString stringWithFormat:@"%@ %d", type, size]; 104 | const char *headerBytes = [header cStringUsingEncoding:NSASCIIStringEncoding]; 105 | 106 | object = [NSMutableData dataWithBytes:headerBytes length:([header length] + 1)]; 107 | [object appendData:objectData]; 108 | 109 | CC_SHA1([object bytes], [object length], rawsha); 110 | [ObjGit gitUnpackHex:rawsha fillSha:sha1]; 111 | NSLog(@"WRITING SHA: %s", sha1); 112 | 113 | // write object to file 114 | shaStr = [NSString stringWithCString:sha1 encoding:NSASCIIStringEncoding]; 115 | path = [self getLooseObjectPathBySha:shaStr]; 116 | NSData *compress = [[NSData dataWithBytes:[object bytes] length:[object length]] compressedData]; 117 | [compress writeToFile:path atomically:YES]; 118 | return shaStr; 119 | } 120 | 121 | - (BOOL) openRepo:(NSString *)dirPath 122 | { 123 | self.gitDirectory = dirPath; 124 | return YES; 125 | } 126 | 127 | - (NSMutableArray *) getCommitsFromSha:(NSString *)shaValue withLimit:(int)commitSize 128 | { 129 | NSString *currentSha; 130 | NSMutableArray *toDoArray = [NSMutableArray arrayWithCapacity:10]; 131 | NSMutableArray *commitArray = [NSMutableArray arrayWithCapacity:commitSize]; 132 | ObjGitCommit *gCommit; 133 | 134 | [toDoArray addObject: shaValue]; 135 | 136 | // loop for commits 137 | while( ([toDoArray count] > 0) && ([commitArray count] < commitSize) ) { 138 | currentSha = [[toDoArray objectAtIndex: 0] retain]; 139 | [toDoArray removeObjectAtIndex:0]; 140 | 141 | NSString *objectPath = [self getLooseObjectPathBySha:currentSha]; 142 | NSFileHandle *fm = [NSFileHandle fileHandleForReadingAtPath:objectPath]; 143 | 144 | gCommit = [[ObjGitCommit alloc] initFromRaw:[fm availableData] withSha:currentSha]; 145 | 146 | [toDoArray addObjectsFromArray:gCommit.parentShas]; 147 | [commitArray addObject:gCommit]; 148 | } 149 | 150 | // NSLog(@"s: %@", commitArray); 151 | //[toDoArray release]; 152 | return commitArray; 153 | } 154 | 155 | - (ObjGitObject *) getObjectFromSha:(NSString *)sha1 156 | { 157 | NSString *objectPath = [self getLooseObjectPathBySha:sha1]; 158 | //NSLog(@"READ FROM FILE: %@", objectPath); 159 | NSFileHandle *fm = [NSFileHandle fileHandleForReadingAtPath:objectPath]; 160 | ObjGitObject *obj = [[ObjGitObject alloc] initFromRaw:[fm availableData] withSha:sha1]; 161 | [fm closeFile]; 162 | return obj; 163 | } 164 | 165 | - (BOOL) hasObject: (NSString *)sha1 166 | { 167 | NSString *path; 168 | path = [self getLooseObjectPathBySha:sha1]; 169 | NSFileManager *fm = [NSFileManager defaultManager]; 170 | if ([fm fileExistsAtPath:path]) { 171 | return YES; 172 | } else { 173 | // TODO : check packs 174 | } 175 | return NO; 176 | } 177 | 178 | - (NSString *) getLooseObjectPathBySha: (NSString *)shaValue 179 | { 180 | NSString *looseSubDir = [shaValue substringWithRange:NSMakeRange(0, 2)]; 181 | NSString *looseFileName = [shaValue substringWithRange:NSMakeRange(2, 38)]; 182 | 183 | NSString *dir = [NSString stringWithFormat: @"%@/objects/%@", self.gitDirectory, looseSubDir]; 184 | 185 | BOOL isDir; 186 | NSFileManager *fm = [NSFileManager defaultManager]; 187 | if (!([fm fileExistsAtPath:dir isDirectory:&isDir] && isDir)) { 188 | [fm createDirectoryAtPath:dir attributes:nil]; 189 | } 190 | 191 | return [NSString stringWithFormat: @"%@/objects/%@/%@", \ 192 | self.gitDirectory, looseSubDir, looseFileName]; 193 | } 194 | 195 | 196 | /* 197 | * returns 1 if the char is alphanumeric, 0 if not 198 | */ 199 | + (int) isAlpha:(unsigned char)n 200 | { 201 | if(n <= 102 && n >= 97) { 202 | return 1; 203 | } 204 | return 0; 205 | } 206 | 207 | /* 208 | * fills a 40-char string with a readable hex version of 20-char sha binary 209 | */ 210 | + (int) gitUnpackHex:(const unsigned char *)rawsha fillSha:(char *)sha1 211 | { 212 | static const char hex[] = "0123456789abcdef"; 213 | int i; 214 | 215 | for (i = 0; i < 20; i++) { 216 | unsigned char n = rawsha[i]; 217 | sha1[i * 2] = hex[((n >> 4) & 15)]; 218 | n <<= 4; 219 | sha1[(i * 2) + 1] = hex[((n >> 4) & 15)]; 220 | } 221 | sha1[40] = 0; 222 | 223 | return 1; 224 | } 225 | 226 | /* 227 | * fills 20-char sha from 40-char hex version 228 | */ 229 | + (int) gitPackHex:(const char *)sha1 fillRawSha:(unsigned char *)rawsha 230 | { 231 | unsigned char byte = 0; 232 | int i, j = 0; 233 | 234 | for (i = 1; i <= 40; i++) { 235 | unsigned char n = sha1[i - 1]; 236 | 237 | if([ObjGit isAlpha:n]) { 238 | byte |= ((n & 15) + 9) & 15; 239 | } else { 240 | byte |= (n & 15); 241 | } 242 | if(i & 1) { 243 | byte <<= 4; 244 | } else { 245 | rawsha[j] = (byte & 0xff); 246 | j++; 247 | byte = 0; 248 | } 249 | } 250 | return 1; 251 | } 252 | 253 | @end 254 | 255 | -------------------------------------------------------------------------------- /ObjGitCommit.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObjGitCommit.h 3 | // ObjGit 4 | // 5 | 6 | #import 7 | #import "ObjGitObject.h" 8 | 9 | @interface ObjGitCommit : NSObject { 10 | NSString *sha; 11 | NSArray *parentShas; 12 | NSString *treeSha; 13 | NSString *author; 14 | NSString *author_email; 15 | NSDate *authored_date; 16 | NSString *committer; 17 | NSString *committer_email; 18 | NSDate *committed_date; 19 | NSString *message; 20 | ObjGitObject *git_object; 21 | } 22 | 23 | @property(copy, readwrite) NSString *sha; 24 | @property(copy, readwrite) NSArray *parentShas; 25 | @property(copy, readwrite) NSString *treeSha; 26 | @property(copy, readwrite) NSString *author; 27 | @property(copy, readwrite) NSString *author_email; 28 | @property(copy, readwrite) NSDate *authored_date; 29 | @property(copy, readwrite) NSString *committer; 30 | @property(copy, readwrite) NSString *committer_email; 31 | @property(retain, readwrite) NSDate *committed_date; 32 | @property(assign, readwrite) NSString *message; 33 | @property(assign, readwrite) ObjGitObject *git_object; 34 | 35 | - (id) initFromGitObject:(ObjGitObject *)gitObject; 36 | - (id) initFromRaw:(NSData *)rawData withSha:(NSString *)shaValue; 37 | - (void) parseContent; 38 | - (void) logObject; 39 | - (NSArray *) authorArray; 40 | - (NSArray *) parseAuthorString:(NSString *)authorString withType:(NSString *)typeString; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ObjGitCommit.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObjGitCommit.m 3 | // ObjGit 4 | // 5 | 6 | #import "ObjGitObject.h" 7 | #import "ObjGitCommit.h" 8 | 9 | @implementation ObjGitCommit 10 | 11 | @synthesize parentShas; 12 | @synthesize treeSha; 13 | @synthesize author; 14 | @synthesize author_email; 15 | @synthesize authored_date; 16 | @synthesize committer; 17 | @synthesize committer_email; 18 | @synthesize committed_date; 19 | @synthesize message; 20 | @synthesize git_object; 21 | @synthesize sha; 22 | 23 | - (id) initFromGitObject:(ObjGitObject *)gitObject { 24 | self = [super init]; 25 | self.sha = [gitObject sha]; 26 | self.git_object = gitObject; 27 | [self parseContent]; 28 | return self; 29 | } 30 | 31 | - (id) initFromRaw:(NSData *)rawData withSha:(NSString *)shaValue 32 | { 33 | self = [super init]; 34 | self.git_object = [[ObjGitObject alloc] initFromRaw:rawData withSha:shaValue]; 35 | self.sha = shaValue; 36 | [self parseContent]; 37 | //[self logObject]; 38 | return self; 39 | } 40 | 41 | - (void) logObject 42 | { 43 | NSLog(@"tree : %@", treeSha); 44 | NSLog(@"author : %@, %@ : %@", author, author_email, authored_date); 45 | NSLog(@"committer: %@, %@ : %@", committer, committer_email, committed_date); 46 | NSLog(@"parents : %@", parentShas); 47 | NSLog(@"message : %@", message); 48 | } 49 | 50 | - (NSArray *) authorArray 51 | { 52 | return [NSArray arrayWithObjects:self.author, self.author_email, self.authored_date, nil]; 53 | } 54 | 55 | - (void) parseContent 56 | { 57 | // extract parent shas, tree sha, author/committer info, message 58 | NSArray *lines = [self.git_object.contents componentsSeparatedByString:@"\n"]; 59 | NSEnumerator *enumerator; 60 | NSMutableArray *parents; 61 | NSMutableString *buildMessage; 62 | NSString *line, *key, *val; 63 | int inMessage = 0; 64 | 65 | buildMessage = [NSMutableString new]; 66 | parents = [NSMutableArray new]; 67 | 68 | enumerator = [lines objectEnumerator]; 69 | while ((line = [enumerator nextObject]) != nil) { 70 | // NSLog(@"line: %@", line); 71 | if(!inMessage) { 72 | if([line length] == 0) { 73 | inMessage = 1; 74 | } else { 75 | NSArray *values = [line componentsSeparatedByString:@" "]; 76 | key = [values objectAtIndex: 0]; 77 | val = [values objectAtIndex: 1]; 78 | if([key isEqualToString: @"tree"]) { 79 | self.treeSha = val; 80 | } else if ([key isEqualToString: @"parent"]) { 81 | [parents addObject: val]; 82 | } else if ([key isEqualToString: @"author"]) { 83 | NSArray *name_email_date = [self parseAuthorString:line withType:@"author "]; 84 | self.author = [name_email_date objectAtIndex: 0]; 85 | self.author_email = [name_email_date objectAtIndex: 1]; 86 | self.authored_date = [name_email_date objectAtIndex: 2]; 87 | } else if ([key isEqualToString: @"committer"]) { 88 | NSArray *name_email_date = [self parseAuthorString:line withType:@"committer "]; 89 | self.committer = [name_email_date objectAtIndex: 0]; 90 | self.committer_email = [name_email_date objectAtIndex: 1]; 91 | self.committed_date = [name_email_date objectAtIndex: 2]; 92 | } 93 | } 94 | } else { 95 | [buildMessage appendString: line]; 96 | [buildMessage appendString: @"\n"]; 97 | } 98 | } 99 | self.message = buildMessage; 100 | self.parentShas = parents; 101 | } 102 | 103 | - (NSArray *) parseAuthorString:(NSString *)authorString withType:(NSString *)typeString 104 | { 105 | NSArray *name_email_date; 106 | name_email_date = [authorString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]; 107 | 108 | NSString *nameVal = [name_email_date objectAtIndex: 0]; 109 | NSString *emailVal = [name_email_date objectAtIndex: 1]; 110 | NSString *dateVal = [name_email_date objectAtIndex: 2]; 111 | NSDate *dateDateVal; 112 | dateDateVal = [NSDate dateWithTimeIntervalSince1970:[dateVal doubleValue]]; 113 | 114 | NSMutableString *tempValue = [[NSMutableString alloc] init]; 115 | [tempValue setString:nameVal]; 116 | [tempValue replaceOccurrencesOfString: typeString 117 | withString: @"" 118 | options: 0 119 | range: NSMakeRange(0, [tempValue length])]; 120 | 121 | return [NSArray arrayWithObjects:tempValue, emailVal, dateDateVal, nil]; 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /ObjGitObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObjGitObject.h 3 | // ObjGit 4 | // 5 | 6 | #import 7 | 8 | @interface ObjGitObject : NSObject { 9 | NSString* sha; 10 | NSInteger size; 11 | NSString* type; 12 | NSString* contents; 13 | char* rawContents; 14 | int rawContentLen; 15 | NSData* raw; 16 | } 17 | 18 | @property(copy, readwrite) NSString *sha; 19 | @property(assign, readwrite) NSInteger size; 20 | @property(copy, readwrite) NSString *type; 21 | @property(copy, readwrite) NSString *contents; 22 | @property(assign, readwrite) char *rawContents; 23 | @property(assign, readwrite) int rawContentLen; 24 | @property(copy, readwrite) NSData *raw; 25 | 26 | - (id) initFromRaw:(NSData *)rawData withSha:(NSString *)shaValue; 27 | - (void) parseRaw; 28 | - (NSData *) inflateRaw:(NSData *)rawData; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /ObjGitObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObjGitObject.m 3 | // ObjGit 4 | // 5 | 6 | #import "ObjGitObject.h" 7 | #import "NSDataCompression.h" 8 | 9 | @implementation ObjGitObject 10 | 11 | @synthesize sha; 12 | @synthesize size; 13 | @synthesize type; 14 | @synthesize contents; 15 | @synthesize raw; 16 | @synthesize rawContents; 17 | @synthesize rawContentLen; 18 | 19 | - (id) initFromRaw:(NSData *)rawData withSha:(NSString *)shaValue 20 | { 21 | self = [super init]; 22 | sha = shaValue; 23 | raw = [self inflateRaw:rawData]; 24 | // NSLog(@"init sha: %@", sha); 25 | // NSLog(@"raw: %@", raw); 26 | [self parseRaw]; 27 | return self; 28 | } 29 | 30 | - (void) parseRaw 31 | { 32 | char *ptr, *bytes = (char *)[raw bytes]; 33 | int len, rest; 34 | len = (ptr = memchr(bytes, nil, len = [raw length])) ? ptr - bytes : len; 35 | rest = [raw length] - len - 1; 36 | 37 | ptr++; 38 | NSString *header = [NSString stringWithCString:bytes length:len]; 39 | contents = [NSString stringWithCString:ptr length:rest]; 40 | 41 | rawContents = malloc(rest); 42 | memcpy(rawContents, ptr, rest); 43 | rawContentLen = rest; 44 | 45 | NSArray *headerData = [header componentsSeparatedByString:@" "]; 46 | 47 | type = [headerData objectAtIndex:0]; 48 | size = [[headerData objectAtIndex:1] intValue]; 49 | } 50 | 51 | - (NSData *) inflateRaw:(NSData *)rawData 52 | { 53 | return [rawData decompressedData]; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /ObjGitServerHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObjGitServerHandler.h 3 | // ObjGit 4 | // 5 | 6 | #import 7 | #include 8 | #import "ObjGitObject.h" 9 | 10 | @interface ObjGitServerHandler : NSObject { 11 | NSInputStream* inStream; 12 | NSOutputStream* outStream; 13 | ObjGit* gitRepo; 14 | NSString* gitPath; 15 | 16 | NSMutableArray* refsRead; 17 | NSMutableArray* needRefs; 18 | NSMutableDictionary* refDict; 19 | 20 | int capabilitiesSent; 21 | } 22 | 23 | @property(assign, readwrite) NSInputStream *inStream; 24 | @property(assign, readwrite) NSOutputStream *outStream; 25 | @property(assign, readwrite) ObjGit *gitRepo; 26 | @property(assign, readwrite) NSString *gitPath; 27 | 28 | @property(assign, readwrite) NSMutableArray *refsRead; 29 | @property(assign, readwrite) NSMutableArray *needRefs; 30 | @property(assign, readwrite) NSMutableDictionary *refDict; 31 | 32 | @property(assign, readwrite) int capabilitiesSent; 33 | 34 | - (void) initWithGit:(ObjGit *)git gitPath:(NSString *)gitRepoPath input:(NSInputStream *)streamIn output:(NSOutputStream *)streamOut; 35 | - (void) handleRequest; 36 | 37 | - (void) uploadPack:(NSString *)repositoryName; 38 | - (void) receiveNeeds; 39 | - (void) uploadPackFile; 40 | - (void) sendNack; 41 | - (void) sendPackData; 42 | 43 | - (void) receivePack:(NSString *)repositoryName; 44 | - (void) gatherObjectShasFromCommit:(NSString *)shaValue; 45 | - (void) gatherObjectShasFromTree:(NSString *)shaValue; 46 | - (void) respondPack:(uint8_t *)buffer length:(int)size checkSum:(CC_SHA1_CTX *)checksum; 47 | 48 | - (void) sendRefs; 49 | - (void) sendRef:(NSString *)refName sha:(NSString *)shaString; 50 | - (void) readRefs; 51 | - (void) readPack; 52 | - (void) writeRefs; 53 | - (NSData *) readData:(int)size; 54 | - (NSString *) typeString:(int)type; 55 | - (int) typeInt:(NSString *)type; 56 | - (void) unpackDeltified:(int)type size:(int)size; 57 | 58 | - (NSData *) patchDelta:(NSData *)deltaData withObject:(ObjGitObject *)gitObject; 59 | - (NSArray *) patchDeltaHeaderSize:(NSData *)deltaData position:(unsigned long)position; 60 | 61 | - (NSString *)readServerSha; 62 | - (int) readPackHeader; 63 | - (void) unpackObject; 64 | 65 | - (void) longVal:(uint32_t)raw toByteBuffer:(uint8_t *)buffer; 66 | - (void) packetFlush; 67 | - (void) writeServer:(NSString *)dataWrite; 68 | - (void) writeServerLength:(unsigned int)length; 69 | - (void) sendPacket:(NSString *)dataSend; 70 | - (NSString *) packetReadLine; 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /ObjGitServerHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObjGitServerHandler.m 3 | // ObjGit 4 | // 5 | 6 | #define NULL_SHA @"0000000000000000000000000000000000000000" 7 | #define CAPABILITIES @" report-status delete-refs " 8 | 9 | #define PACK_SIGNATURE 0x5041434b /* "PACK" */ 10 | #define PACK_VERSION 2 11 | 12 | #define OBJ_NONE 0 13 | #define OBJ_COMMIT 1 14 | #define OBJ_TREE 2 15 | #define OBJ_BLOB 3 16 | #define OBJ_TAG 4 17 | #define OBJ_OFS_DELTA 6 18 | #define OBJ_REF_DELTA 7 19 | 20 | #import "ObjGit.h" 21 | #import "ObjGitObject.h" 22 | #import "ObjGitCommit.h" 23 | #import "ObjGitTree.h" 24 | #import "ObjGitServerHandler.h" 25 | #import "NSDataCompression.h" 26 | #include 27 | #include 28 | 29 | @implementation ObjGitServerHandler 30 | 31 | @synthesize inStream; 32 | @synthesize outStream; 33 | @synthesize gitRepo; 34 | @synthesize gitPath; 35 | 36 | @synthesize refsRead; 37 | @synthesize needRefs; 38 | @synthesize refDict; 39 | 40 | @synthesize capabilitiesSent; 41 | 42 | - (void) initWithGit:(ObjGit *)git gitPath:(NSString *)gitRepoPath input:(NSInputStream *)streamIn output:(NSOutputStream *)streamOut 43 | { 44 | gitRepo = git; 45 | gitPath = gitRepoPath; 46 | inStream = streamIn; 47 | outStream = streamOut; 48 | [self handleRequest]; 49 | } 50 | 51 | /* 52 | * initiates communication with an incoming request 53 | * and passes it to the appropriate receiving function 54 | * either upload-pack for fetches or receive-pack for pushes 55 | */ 56 | - (void) handleRequest { 57 | NSLog(@"HANDLE REQUEST"); 58 | NSString *header, *command, *repository, *repo, *hostpath; 59 | header = [self packetReadLine]; 60 | 61 | NSArray *values = [header componentsSeparatedByString:@" "]; 62 | command = [values objectAtIndex: 0]; 63 | repository = [values objectAtIndex: 1]; 64 | 65 | values = [repository componentsSeparatedByCharactersInSet:[NSCharacterSet controlCharacterSet]]; 66 | repo = [values objectAtIndex: 0]; 67 | hostpath = [values objectAtIndex: 1]; 68 | 69 | NSLog(@"header: %@ : %@ : %@", command, repo, hostpath); 70 | 71 | NSString *dir = [gitPath stringByAppendingPathComponent:repo]; 72 | [gitRepo openRepo:dir]; 73 | 74 | if([command isEqualToString: @"git-receive-pack"]) { // git push // 75 | [self receivePack:repository]; 76 | } else if ([command isEqualToString: @"git-upload-pack"]) { // git fetch // 77 | [self uploadPack:repository]; 78 | } 79 | 80 | } 81 | 82 | /*** UPLOAD-PACK FUNCTIONS ***/ 83 | 84 | - (void) uploadPack:(NSString *)repositoryName { 85 | [self sendRefs]; 86 | [self receiveNeeds]; 87 | [self uploadPackFile]; 88 | //NSLog(@"out:%@", outStream); 89 | //NSLog(@"out avail:%d", [outStream hasSpaceAvailable]); 90 | //NSLog(@" in avail:%d", [inStream hasBytesAvailable]); 91 | } 92 | 93 | - (void) receiveNeeds 94 | { 95 | NSLog(@"receive needs"); 96 | NSString *data, *cmd, *sha; 97 | NSArray *values; 98 | needRefs = [[NSMutableArray alloc] init]; 99 | 100 | while(![(data = [self packetReadLine]) isEqualToString:@"done\n"]) { 101 | if([data length] > 40) { 102 | NSLog(@"data line: %@", data); 103 | 104 | values = [data componentsSeparatedByString:@" "]; 105 | cmd = [values objectAtIndex: 0]; 106 | sha = [values objectAtIndex: 1]; 107 | 108 | [needRefs addObject:values]; 109 | } 110 | } 111 | 112 | //puts @session.recv(9) 113 | NSLog(@"need refs:%@", needRefs); 114 | NSLog(@"sending nack"); 115 | [self sendNack]; 116 | } 117 | 118 | - (void) uploadPackFile 119 | { 120 | NSLog(@"upload pack file"); 121 | NSString *command, *shaValue; 122 | NSArray *thisRef; 123 | 124 | refDict = [[NSMutableDictionary alloc] init]; 125 | 126 | NSEnumerator *e = [needRefs objectEnumerator]; 127 | while ( (thisRef = [e nextObject]) ) { 128 | command = [thisRef objectAtIndex:0]; 129 | shaValue = [thisRef objectAtIndex:1]; 130 | if([command isEqualToString:@"have"]) { 131 | [refDict setObject:@"have" forKey:shaValue]; 132 | } 133 | } 134 | 135 | NSLog(@"gathering shas"); 136 | e = [needRefs objectEnumerator]; 137 | while ( (thisRef = [e nextObject]) ) { 138 | command = [thisRef objectAtIndex:0]; 139 | shaValue = [thisRef objectAtIndex:1]; 140 | if([command isEqualToString:@"want"]) { 141 | [self gatherObjectShasFromCommit:shaValue]; 142 | } 143 | } 144 | 145 | [self sendPackData]; 146 | } 147 | 148 | - (void) sendPackData 149 | { 150 | NSLog(@"send pack data"); 151 | NSString *current; 152 | NSEnumerator *e; 153 | 154 | CC_SHA1_CTX *checksum; 155 | CC_SHA1_Init(checksum); 156 | 157 | //NSArray *shas; 158 | //shas = [refDict keysSortedByValueUsingSelector:@selector(compare:)]; 159 | 160 | uint8_t buffer[5]; 161 | 162 | // write pack header 163 | 164 | [self longVal:htonl(PACK_SIGNATURE) toByteBuffer:buffer]; 165 | [self respondPack:buffer length:4 checkSum:checksum]; 166 | 167 | [self longVal:htonl(PACK_VERSION) toByteBuffer:buffer]; 168 | [self respondPack:buffer length:4 checkSum:checksum]; 169 | 170 | [self longVal:htonl([refDict count]) toByteBuffer:buffer]; 171 | NSLog(@"write len [%d %d %d %d]", buffer[0], buffer[1], buffer[2], buffer[3]); 172 | [self respondPack:buffer length:4 checkSum:checksum]; 173 | 174 | //NSLog(@"refs: %@", shas); 175 | e = [refDict keyEnumerator]; 176 | ObjGitObject *obj; 177 | NSData *objData, *data; 178 | int size, btype, c; 179 | while ( (current = [e nextObject]) ) { 180 | obj = [gitRepo getObjectFromSha:current]; 181 | size = [obj size]; 182 | btype = [self typeInt:[obj type]]; 183 | 184 | c = (btype << 4) | (size & 15); 185 | size = (size >> 4); 186 | if(size > 0) 187 | c |= 0x80; 188 | buffer[0] = c; 189 | [self respondPack:buffer length:1 checkSum:checksum]; 190 | 191 | while (size > 0) { 192 | c = size & 0x7f; 193 | size = (size >> 7); 194 | if(size > 0) 195 | c |= 0x80; 196 | buffer[0] = c; 197 | [self respondPack:buffer length:1 checkSum:checksum]; 198 | } 199 | 200 | // pack object data 201 | //NSLog(@"srclen:%d, %d", [obj size], [obj rawContentLen]); 202 | objData = [NSData dataWithBytes:[obj rawContents] length:([obj rawContentLen])]; 203 | data = [objData compressedData]; 204 | 205 | int len = [data length]; 206 | uint8_t dataBuffer[len + 1]; 207 | [data getBytes:dataBuffer]; 208 | 209 | [self respondPack:dataBuffer length:len checkSum:checksum]; 210 | } 211 | 212 | unsigned char finalSha[20]; 213 | CC_SHA1_Final(finalSha, checksum); 214 | 215 | [outStream write:finalSha maxLength:20]; 216 | NSLog(@"end sent"); 217 | } 218 | 219 | - (void) respondPack:(uint8_t *)buffer length:(int)size checkSum:(CC_SHA1_CTX *)checksum 220 | { 221 | CC_SHA1_Update(checksum, buffer, size); 222 | [outStream write:buffer maxLength:size]; 223 | } 224 | 225 | - (void) longVal:(uint32_t)raw toByteBuffer:(uint8_t *)buffer 226 | { 227 | buffer[3] = (raw >> 24); 228 | buffer[2] = (raw >> 16); 229 | buffer[1] = (raw >> 8); 230 | buffer[0] = (raw); 231 | } 232 | 233 | - (void) gatherObjectShasFromCommit:(NSString *)shaValue 234 | { 235 | NSString *parentSha; 236 | ObjGitCommit *commit = [[ObjGitCommit alloc] initFromGitObject:[gitRepo getObjectFromSha:shaValue]]; 237 | [refDict setObject:@"_commit" forKey:shaValue]; 238 | 239 | // add the tree objects 240 | [self gatherObjectShasFromTree:[commit treeSha]]; 241 | 242 | NSArray *parents = [commit parentShas]; 243 | 244 | NSEnumerator *e = [parents objectEnumerator]; 245 | while ( (parentSha = [e nextObject]) ) { 246 | NSLog(@"parent sha:%@", parentSha); 247 | // TODO : check that refDict does not have this 248 | [self gatherObjectShasFromCommit:parentSha]; 249 | } 250 | } 251 | 252 | - (void) gatherObjectShasFromTree:(NSString *)shaValue 253 | { 254 | ObjGitTree *tree = [ObjGitTree alloc]; 255 | [tree initFromGitObject:[gitRepo getObjectFromSha:shaValue]]; 256 | [refDict setObject:@"/" forKey:shaValue]; 257 | NSEnumerator *e = [[tree treeEntries] objectEnumerator]; 258 | //[tree release]; 259 | NSArray *entries; 260 | NSString *name, *sha, *mode; 261 | while ( (entries = [e nextObject]) ) { 262 | mode = [entries objectAtIndex:0]; 263 | name = [entries objectAtIndex:1]; 264 | sha = [entries objectAtIndex:2]; 265 | [refDict setObject:name forKey:sha]; 266 | if ([mode isEqualToString:@"40000"]) { // tree 267 | // TODO : check that refDict does not have this 268 | [self gatherObjectShasFromTree:sha]; 269 | } 270 | } 271 | } 272 | 273 | 274 | - (void) sendNack 275 | { 276 | [self sendPacket:@"0007NAK"]; 277 | } 278 | 279 | 280 | /*** UPLOAD-PACK FUNCTIONS END ***/ 281 | 282 | 283 | 284 | /*** RECEIVE-PACK FUNCTIONS ***/ 285 | 286 | /* 287 | * handles a push request - this involves validating the request, 288 | * initializing the repository if it's not there, sending the 289 | * refs we have, receiving the packfile form the client and unpacking 290 | * the packed objects (eventually we should have an option to keep the 291 | * packfile and build an index instead) 292 | */ 293 | - (void) receivePack:(NSString *)repositoryName { 294 | capabilitiesSent = 0; 295 | 296 | [gitRepo ensureGitPath]; 297 | 298 | [self sendRefs]; 299 | [self readRefs]; 300 | [self readPack]; 301 | [self writeRefs]; 302 | [self packetFlush]; 303 | } 304 | 305 | - (void) sendRefs { 306 | NSLog(@"send refs"); 307 | 308 | NSArray *refs = [gitRepo getAllRefs]; 309 | NSLog(@"refs: %@", refs); 310 | 311 | NSEnumerator *e = [refs objectEnumerator]; 312 | NSString *refName, *shaValue; 313 | NSArray *thisRef; 314 | while ( (thisRef = [e nextObject]) ) { 315 | refName = [thisRef objectAtIndex:0]; 316 | shaValue = [thisRef objectAtIndex:1]; 317 | [self sendRef:refName sha:shaValue]; 318 | } 319 | 320 | // send capabilities and null sha to client if no refs // 321 | if(!capabilitiesSent) 322 | [self sendRef:@"capabilities^{}" sha:NULL_SHA]; 323 | [self packetFlush]; 324 | } 325 | 326 | - (void) sendRef:(NSString *)refName sha:(NSString *)shaString { 327 | NSString *sendData; 328 | if(capabilitiesSent) 329 | sendData = [[NSString alloc] initWithFormat:@"%@ %@\n", shaString, refName]; 330 | else 331 | sendData = [[NSString alloc] initWithFormat:@"%@ %@\0%@\n", shaString, refName, CAPABILITIES]; 332 | [self writeServer:sendData]; 333 | capabilitiesSent = 1; 334 | } 335 | 336 | - (void) readRefs { 337 | NSString *data, *old, *new, *refName, *cap, *refStuff; 338 | NSLog(@"read refs"); 339 | data = [self packetReadLine]; 340 | NSMutableArray *refs = [[NSMutableArray alloc] init]; 341 | while([data length] > 0) { 342 | 343 | NSArray *values = [data componentsSeparatedByString:@" "]; 344 | old = [values objectAtIndex:0]; 345 | new = [values objectAtIndex:1]; 346 | refStuff = [values objectAtIndex:2]; 347 | 348 | NSArray *ref = [refStuff componentsSeparatedByString:@"\0"]; 349 | refName = [ref objectAtIndex:0]; 350 | cap = nil; 351 | if([ref count] > 1) 352 | cap = [ref objectAtIndex:1]; 353 | 354 | NSArray *refData = [NSArray arrayWithObjects:old, new, refName, cap, nil]; 355 | [refs addObject:refData]; // save the refs for writing later 356 | 357 | /* DEBUGGING */ 358 | NSLog(@"ref: [%@ : %@ : %@]", old, new, refName, cap); 359 | 360 | data = [self packetReadLine]; 361 | } 362 | refsRead = [NSArray arrayWithArray:refs]; 363 | } 364 | 365 | /* 366 | * read packfile data from the stream and expand the objects out to disk 367 | */ 368 | - (void) readPack { 369 | NSLog(@"read pack"); 370 | int n; 371 | int entries = [self readPackHeader]; 372 | 373 | for(n = 1; n <= entries; n++) { 374 | NSLog(@"entry: %d", n); 375 | [self unpackObject]; 376 | } 377 | 378 | // receive and process checksum 379 | uint8_t checksum[20]; 380 | [inStream read:checksum maxLength:20]; 381 | } 382 | 383 | - (void) unpackObject { 384 | // read in the header 385 | int size, type, shift; 386 | uint8_t byte[1]; 387 | [inStream read:byte maxLength:1]; 388 | 389 | size = byte[0] & 0xf; 390 | type = (byte[0] >> 4) & 7; 391 | shift = 4; 392 | while((byte[0] & 0x80) != 0) { 393 | [inStream read:byte maxLength:1]; 394 | size |= ((byte[0] & 0x7f) << shift); 395 | shift += 7; 396 | } 397 | 398 | NSLog(@"TYPE: %d", type); 399 | NSLog(@"size: %d", size); 400 | 401 | if((type == OBJ_COMMIT) || (type == OBJ_TREE) || (type == OBJ_BLOB) || (type == OBJ_TAG)) { 402 | NSData *objectData; 403 | objectData = [self readData:size]; 404 | [gitRepo writeObject:objectData withType:[self typeString:type] withSize:size]; 405 | // TODO : check saved delta objects 406 | } else if ((type == OBJ_REF_DELTA) || (type == OBJ_OFS_DELTA)) { 407 | [self unpackDeltified:type size:size]; 408 | } else { 409 | NSLog(@"bad object type %d", type); 410 | } 411 | } 412 | 413 | - (void) unpackDeltified:(int)type size:(int)size { 414 | if(type == OBJ_REF_DELTA) { 415 | NSString *sha1; 416 | NSData *objectData, *contents; 417 | 418 | sha1 = [self readServerSha]; 419 | //NSLog(@"DELTA SHA: %@", sha1); 420 | objectData = [self readData:size]; 421 | 422 | if([gitRepo hasObject:sha1]) { 423 | ObjGitObject *object; 424 | object = [gitRepo getObjectFromSha:sha1]; 425 | contents = [self patchDelta:objectData withObject:object]; 426 | //NSLog(@"unpacked delta: %@ : %@", contents, [object type]); 427 | [gitRepo writeObject:contents withType:[object type] withSize:[contents length]]; 428 | //[object release]; 429 | } else { 430 | // TODO : OBJECT ISN'T HERE YET, SAVE THIS DELTA FOR LATER // 431 | /* 432 | @delta_list[sha1] ||= [] 433 | @delta_list[sha1] << delta 434 | */ 435 | } 436 | } else { 437 | // offset deltas not supported yet 438 | // this isn't returned in the capabilities, so it shouldn't be a problem 439 | } 440 | } 441 | 442 | - (NSData *) patchDelta:(NSData *)deltaData withObject:(ObjGitObject *)gitObject 443 | { 444 | unsigned long sourceSize, destSize, position; 445 | unsigned long cp_off, cp_size; 446 | unsigned char c[2], d[2]; 447 | 448 | int buffLength = 1000; 449 | NSMutableData *buffer = [[NSMutableData alloc] initWithCapacity:buffLength]; 450 | 451 | NSArray *sizePos = [self patchDeltaHeaderSize:deltaData position:0]; 452 | sourceSize = [[sizePos objectAtIndex:0] longValue]; 453 | position = [[sizePos objectAtIndex:1] longValue]; 454 | 455 | //NSLog(@"SS: %d Pos:%d", sourceSize, position); 456 | 457 | sizePos = [self patchDeltaHeaderSize:deltaData position:position]; 458 | destSize = [[sizePos objectAtIndex:0] longValue]; 459 | position = [[sizePos objectAtIndex:1] longValue]; 460 | 461 | NSData *source = [NSData dataWithBytes:[gitObject rawContents] length:[gitObject rawContentLen]]; 462 | 463 | //NSLog(@"SOURCE:%@", source); 464 | NSMutableData *destination = [NSMutableData dataWithCapacity:destSize]; 465 | 466 | while (position < ([deltaData length])) { 467 | [deltaData getBytes:c range:NSMakeRange(position, 1)]; 468 | //NSLog(@"DS: %d Pos:%d", destSize, position); 469 | //NSLog(@"CHR: %d", c[0]); 470 | 471 | position += 1; 472 | if((c[0] & 0x80) != 0) { 473 | position -= 1; 474 | cp_off = cp_size = 0; 475 | 476 | if((c[0] & 0x01) != 0) { 477 | [deltaData getBytes:d range:NSMakeRange(position += 1, 1)]; 478 | cp_off = d[0]; 479 | } 480 | if((c[0] & 0x02) != 0) { 481 | [deltaData getBytes:d range:NSMakeRange(position += 1, 1)]; 482 | cp_off |= d[0] << 8; 483 | } 484 | if((c[0] & 0x04) != 0) { 485 | [deltaData getBytes:d range:NSMakeRange(position += 1, 1)]; 486 | cp_off |= d[0] << 16; 487 | } 488 | if((c[0] & 0x08) != 0) { 489 | [deltaData getBytes:d range:NSMakeRange(position += 1, 1)]; 490 | cp_off |= d[0] << 24; 491 | } 492 | if((c[0] & 0x10) != 0) { 493 | [deltaData getBytes:d range:NSMakeRange(position += 1, 1)]; 494 | cp_size = d[0]; 495 | } 496 | if((c[0] & 0x20) != 0) { 497 | [deltaData getBytes:d range:NSMakeRange(position += 1, 1)]; 498 | cp_size |= d[0] << 8; 499 | } 500 | if((c[0] & 0x40) != 0) { 501 | [deltaData getBytes:d range:NSMakeRange(position += 1, 1)]; 502 | cp_size |= d[0] << 16; 503 | } 504 | if(cp_size == 0) 505 | cp_size = 0x10000; 506 | 507 | position += 1; 508 | //NSLog(@"pos: %d", position); 509 | //NSLog(@"offset: %d, %d", cp_off, cp_size); 510 | 511 | if(cp_size > buffLength) { 512 | buffLength = cp_size + 1; 513 | [buffer setLength:buffLength]; 514 | } 515 | 516 | [source getBytes:[buffer mutableBytes] range:NSMakeRange(cp_off, cp_size)]; 517 | [destination appendBytes:[buffer bytes] length:cp_size]; 518 | //NSLog(@"dest: %@", destination); 519 | } else if(c[0] != 0) { 520 | if(c[0] > destSize) 521 | break; 522 | //NSLog(@"thingy: %d, %d", position, c[0]); 523 | [deltaData getBytes:[buffer mutableBytes] range:NSMakeRange(position, c[0])]; 524 | [destination appendBytes:[buffer bytes] length:c[0]]; 525 | position += c[0]; 526 | destSize -= c[0]; 527 | } else { 528 | NSLog(@"invalid delta data"); 529 | } 530 | } 531 | return destination; 532 | } 533 | 534 | - (NSArray *) patchDeltaHeaderSize:(NSData *)deltaData position:(unsigned long)position 535 | { 536 | unsigned long size = 0; 537 | int shift = 0; 538 | unsigned char c[2]; 539 | 540 | do { 541 | [deltaData getBytes:c range:NSMakeRange(position, 1)]; 542 | //NSLog(@"read bytes:%d %d", c[0], position); 543 | position += 1; 544 | size |= (c[0] & 0x7f) << shift; 545 | shift += 7; 546 | } while ( (c[0] & 0x80) != 0 ); 547 | 548 | return [NSArray arrayWithObjects:[NSNumber numberWithLong:size], [NSNumber numberWithLong:position], nil]; 549 | } 550 | 551 | - (NSString *) readServerSha 552 | { 553 | char sha[41]; 554 | uint8_t rawsha[20]; 555 | [inStream read:rawsha maxLength:20]; 556 | [ObjGit gitUnpackHex:rawsha fillSha:sha]; 557 | return [[NSString alloc] initWithBytes:sha length:40 encoding:NSASCIIStringEncoding]; 558 | } 559 | 560 | - (NSString *) typeString:(int)type { 561 | if (type == OBJ_COMMIT) 562 | return @"commit"; 563 | if (type == OBJ_TREE) 564 | return @"tree"; 565 | if (type == OBJ_BLOB) 566 | return @"blob"; 567 | if (type == OBJ_TAG) 568 | return @"tag"; 569 | return @""; 570 | } 571 | 572 | - (int) typeInt:(NSString *)type { 573 | if([type isEqualToString:@"commit"]) 574 | return OBJ_COMMIT; 575 | if([type isEqualToString:@"tree"]) 576 | return OBJ_TREE; 577 | if([type isEqualToString:@"blob"]) 578 | return OBJ_BLOB; 579 | if([type isEqualToString:@"tag"]) 580 | return OBJ_TAG; 581 | return 0; 582 | } 583 | 584 | - (NSData *) readData:(int)size { 585 | // read in the data 586 | NSMutableData *decompressed = [NSMutableData dataWithLength: size]; 587 | BOOL done = NO; 588 | int status; 589 | 590 | uint8_t buffer[2]; 591 | [inStream read:buffer maxLength:1]; 592 | 593 | z_stream strm; 594 | strm.next_in = buffer; 595 | strm.avail_in = 1; 596 | strm.total_out = 0; 597 | strm.zalloc = Z_NULL; 598 | strm.zfree = Z_NULL; 599 | 600 | if (inflateInit (&strm) != Z_OK) 601 | NSLog(@"Inflate Issue"); 602 | 603 | while (!done) 604 | { 605 | // Make sure we have enough room and reset the lengths. 606 | if (strm.total_out >= [decompressed length]) 607 | [decompressed increaseLengthBy: 100]; 608 | strm.next_out = [decompressed mutableBytes] + strm.total_out; 609 | strm.avail_out = [decompressed length] - strm.total_out; 610 | 611 | // Inflate another chunk. 612 | status = inflate (&strm, Z_SYNC_FLUSH); 613 | if (status == Z_STREAM_END) done = YES; 614 | else if (status != Z_OK) { 615 | NSLog(@"status for break: %d", status); 616 | break; 617 | } 618 | 619 | if(!done) { 620 | [inStream read:buffer maxLength:1]; 621 | strm.next_in = buffer; 622 | strm.avail_in = 1; 623 | } 624 | } 625 | if (inflateEnd (&strm) != Z_OK) 626 | NSLog(@"Inflate Issue"); 627 | 628 | // Set real length. 629 | if (done) 630 | [decompressed setLength: strm.total_out]; 631 | 632 | return decompressed; 633 | } 634 | 635 | - (int) readPackHeader { 636 | NSLog(@"read pack header"); 637 | 638 | uint8_t inSig[4], inVer[4], inEntries[4]; 639 | uint32_t version, entries; 640 | [inStream read:inSig maxLength:4]; 641 | [inStream read:inVer maxLength:4]; 642 | [inStream read:inEntries maxLength:4]; 643 | 644 | entries = (inEntries[0] << 24) | (inEntries[1] << 16) | (inEntries[2] << 8) | inEntries[3]; 645 | version = (inVer[0] << 24) | (inVer[1] << 16) | (inVer[2] << 8) | inVer[3]; 646 | if(version == 2) 647 | return entries; 648 | else 649 | return 0; 650 | } 651 | 652 | /* 653 | * write refs to disk after successful read 654 | */ 655 | - (void) writeRefs { 656 | NSLog(@"write refs"); 657 | NSEnumerator *e = [refsRead objectEnumerator]; 658 | NSArray *thisRef; 659 | NSString *toSha, *refName, *sendOk; 660 | 661 | [self writeServer:@"unpack ok\n"]; 662 | 663 | while ( (thisRef = [e nextObject]) ) { 664 | NSLog(@"ref: %@", thisRef); 665 | toSha = [thisRef objectAtIndex:1]; 666 | refName = [thisRef objectAtIndex:2]; 667 | [gitRepo updateRef:refName toSha:toSha]; 668 | sendOk = [NSString stringWithFormat:@"ok %@\n", refName]; 669 | [self writeServer:sendOk]; 670 | } 671 | } 672 | 673 | 674 | /*** NETWORK FUNCTIONS ***/ 675 | 676 | - (void) packetFlush { 677 | [self sendPacket:@"0000"]; 678 | } 679 | 680 | - (void) sendPacket:(NSString *)dataWrite { 681 | NSLog(@"send:[%@]", dataWrite); 682 | int len = [dataWrite length]; 683 | uint8_t buffer[len]; 684 | [[dataWrite dataUsingEncoding:NSUTF8StringEncoding] getBytes:buffer]; 685 | [outStream write:buffer maxLength:len]; 686 | } 687 | 688 | // FROM GIT : pkt-line.c : Linus // 689 | 690 | #define hex(a) (hexchar[(a) & 15]) 691 | - (void) writeServer:(NSString *)dataWrite { 692 | //NSLog(@"write:[%@]", dataWrite); 693 | unsigned int len = [dataWrite length]; 694 | len += 4; 695 | [self writeServerLength:len]; 696 | //NSLog(@"write data"); 697 | [self sendPacket:dataWrite]; 698 | } 699 | 700 | - (void) writeServerLength:(unsigned int)length 701 | { 702 | static char hexchar[] = "0123456789abcdef"; 703 | uint8_t buffer[5]; 704 | 705 | buffer[0] = hex(length >> 12); 706 | buffer[1] = hex(length >> 8); 707 | buffer[2] = hex(length >> 4); 708 | buffer[3] = hex(length); 709 | 710 | //NSLog(@"write len [%c %c %c %c]", buffer[0], buffer[1], buffer[2], buffer[3]); 711 | [outStream write:buffer maxLength:4]; 712 | } 713 | 714 | - (NSString *) packetReadLine { 715 | uint8_t linelen[4]; 716 | unsigned int len = 0; 717 | len = [inStream read:linelen maxLength:4]; 718 | 719 | if(!len) { 720 | if ([inStream streamStatus] != NSStreamStatusAtEnd) 721 | NSLog(@"protocol error: read error"); 722 | return nil; 723 | } 724 | 725 | int n; 726 | len = 0; 727 | for (n = 0; n < 4; n++) { 728 | unsigned char c = linelen[n]; 729 | len <<= 4; 730 | if (c >= '0' && c <= '9') { 731 | len += c - '0'; 732 | continue; 733 | } 734 | if (c >= 'a' && c <= 'f') { 735 | len += c - 'a' + 10; 736 | continue; 737 | } 738 | if (c >= 'A' && c <= 'F') { 739 | len += c - 'A' + 10; 740 | continue; 741 | } 742 | NSLog(@"protocol error: bad line length character"); 743 | } 744 | 745 | if (!len) 746 | return @""; 747 | 748 | len -= 4; 749 | uint8_t data[len + 1]; 750 | 751 | [inStream read:data maxLength:len]; 752 | data[len] = 0; 753 | 754 | return [[NSString alloc] initWithBytes:data length:len encoding:NSASCIIStringEncoding]; 755 | } 756 | 757 | @end 758 | -------------------------------------------------------------------------------- /ObjGitTree.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObjGitTree.h 3 | // ObjGit 4 | // 5 | 6 | #import 7 | #import "ObjGitObject.h" 8 | 9 | @interface ObjGitTree : NSObject { 10 | NSArray *treeEntries; 11 | ObjGitObject *gitObject; 12 | } 13 | 14 | @property(copy, readwrite) NSArray *treeEntries; 15 | @property(assign, readwrite) ObjGitObject *gitObject; 16 | 17 | - (id) initFromGitObject:(ObjGitObject *)object; 18 | - (void) parseContent; 19 | - (void) logObject; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ObjGitTree.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObjGitTree.m 3 | // ObjGit 4 | // 5 | 6 | #import "ObjGit.h" 7 | #import "ObjGitObject.h" 8 | #import "ObjGitTree.h" 9 | 10 | @implementation ObjGitTree 11 | 12 | @synthesize treeEntries; 13 | @synthesize gitObject; 14 | 15 | - (id) initFromGitObject:(ObjGitObject *)object { 16 | self = [super init]; 17 | self.gitObject = object; 18 | [self parseContent]; 19 | return self; 20 | } 21 | 22 | - (void) logObject 23 | { 24 | NSLog(@"entries : %@", treeEntries); 25 | } 26 | 27 | // 100644 testfile\0[20 char sha] 28 | - (void) parseContent 29 | { 30 | char *contents = [self.gitObject rawContents]; 31 | 32 | char mode[9]; 33 | int modePtr = 0; 34 | char name[255]; 35 | int namePtr = 0; 36 | char sha[41]; 37 | unsigned char rawsha[20]; 38 | 39 | NSString *shaStr, *modeStr, *nameStr; 40 | NSArray *entry; 41 | NSMutableArray *entries; 42 | entries = [[NSMutableArray alloc] init]; 43 | 44 | int i, j, state; 45 | state = 1; 46 | 47 | for(i = 0; i < [self.gitObject rawContentLen] - 1; i++) { 48 | if(contents[i] == 0) { 49 | state = 1; 50 | for(j = 0; j < 20; j++) 51 | rawsha[j] = contents[i + j + 1]; 52 | i += 20; 53 | [ObjGit gitUnpackHex:rawsha fillSha:sha]; 54 | 55 | mode[modePtr] = 0; 56 | name[namePtr] = 0; 57 | 58 | shaStr = [[NSString alloc] initWithBytes:sha length:40 encoding:NSASCIIStringEncoding]; 59 | modeStr = [[NSString alloc] initWithBytes:mode length:modePtr encoding:NSASCIIStringEncoding]; 60 | nameStr = [[NSString alloc] initWithBytes:name length:namePtr encoding:NSUTF8StringEncoding]; 61 | 62 | entry = [NSArray arrayWithObjects:modeStr, nameStr, shaStr, nil]; 63 | [entries addObject:entry]; 64 | 65 | modePtr = 0; 66 | namePtr = 0; 67 | } else { // contents 68 | if(contents[i] == 32) { 69 | state = 2; 70 | } else if(state == 1) { // mode 71 | mode[modePtr] = contents[i]; 72 | modePtr++; 73 | } else { // name 74 | name[namePtr] = contents[i]; 75 | namePtr++; 76 | } 77 | } 78 | } 79 | treeEntries = entries; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Objective Git 2 | =================== 3 | 4 | This is a implementation of Git in Objective-C, for embedding in Cocoa 5 | and iPhone applications. 6 | 7 | Initially, it is meant to provide basic read and write access to loose 8 | and packed objects, and to provide an input stream handler that can negotiate 9 | the server side of the git client/server protocol. 10 | 11 | 12 | Credits 13 | =================== 14 | A bit of this code is borrowed from the Git.git project and the NSData 15 | compression stuff was borrowed from the Etoile framework. 16 | 17 | 18 | Authors 19 | =================== 20 | Scott Chacon 21 | --------------------------------------------------------------------------------