PageRenderTime 61ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/PBGitRepository.m

https://github.com/dgrijalva/gitx
Objective C | 987 lines | 773 code | 185 blank | 29 comment | 146 complexity | 02cbba49e4a964f8e73d6a553f13869b MD5 | raw file
Possible License(s): GPL-2.0
  1. //
  2. // PBGitRepository.m
  3. // GitTest
  4. //
  5. // Created by Pieter de Bie on 13-06-08.
  6. // Copyright 2008 __MyCompanyName__. All rights reserved.
  7. //
  8. #import "PBGitRepository.h"
  9. #import "PBGitCommit.h"
  10. #import "PBGitWindowController.h"
  11. #import "PBGitBinary.h"
  12. #import "NSFileHandleExt.h"
  13. #import "PBEasyPipe.h"
  14. #import "PBGitRef.h"
  15. #import "PBGitRevSpecifier.h"
  16. #import "PBRemoteProgressSheet.h"
  17. #import "PBGitRevList.h"
  18. #import "PBGitDefaults.h"
  19. NSString* PBGitRepositoryErrorDomain = @"GitXErrorDomain";
  20. @implementation PBGitRepository
  21. @synthesize revisionList, branches, currentBranch, refs, hasChanged, config;
  22. @synthesize currentBranchFilter;
  23. - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
  24. {
  25. if (outError) {
  26. *outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain
  27. code:0
  28. userInfo:[NSDictionary dictionaryWithObject:@"Reading files is not supported." forKey:NSLocalizedFailureReasonErrorKey]];
  29. }
  30. return NO;
  31. }
  32. + (BOOL) isBareRepository: (NSString*) path
  33. {
  34. return [[PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:[NSArray arrayWithObjects:@"rev-parse", @"--is-bare-repository", nil] inDir:path] isEqualToString:@"true"];
  35. }
  36. + (NSURL*)gitDirForURL:(NSURL*)repositoryURL;
  37. {
  38. if (![PBGitBinary path])
  39. return nil;
  40. NSString* repositoryPath = [repositoryURL path];
  41. if ([self isBareRepository:repositoryPath])
  42. return repositoryURL;
  43. // Use rev-parse to find the .git dir for the repository being opened
  44. NSString* newPath = [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:[NSArray arrayWithObjects:@"rev-parse", @"--git-dir", nil] inDir:repositoryPath];
  45. if ([newPath isEqualToString:@".git"])
  46. return [NSURL fileURLWithPath:[repositoryPath stringByAppendingPathComponent:@".git"]];
  47. if ([newPath length] > 0)
  48. return [NSURL fileURLWithPath:newPath];
  49. return nil;
  50. }
  51. // For a given path inside a repository, return either the .git dir
  52. // (for a bare repo) or the directory above the .git dir otherwise
  53. + (NSURL*)baseDirForURL:(NSURL*)repositoryURL;
  54. {
  55. NSURL* gitDirURL = [self gitDirForURL:repositoryURL];
  56. NSString* repositoryPath = [gitDirURL path];
  57. if (![self isBareRepository:repositoryPath]) {
  58. repositoryURL = [NSURL fileURLWithPath:[[repositoryURL path] stringByDeletingLastPathComponent]];
  59. }
  60. return repositoryURL;
  61. }
  62. // NSFileWrapper is broken and doesn't work when called on a directory containing a large number of directories and files.
  63. //because of this it is safer to implement readFromURL than readFromFileWrapper.
  64. //Because NSFileManager does not attempt to recursively open all directories and file when fileExistsAtPath is called
  65. //this works much better.
  66. - (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
  67. {
  68. if (![PBGitBinary path])
  69. {
  70. if (outError) {
  71. NSDictionary* userInfo = [NSDictionary dictionaryWithObject:[PBGitBinary notFoundError]
  72. forKey:NSLocalizedRecoverySuggestionErrorKey];
  73. *outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain code:0 userInfo:userInfo];
  74. }
  75. return NO;
  76. }
  77. BOOL isDirectory = FALSE;
  78. [[NSFileManager defaultManager] fileExistsAtPath:[absoluteURL path] isDirectory:&isDirectory];
  79. if (!isDirectory) {
  80. if (outError) {
  81. NSDictionary* userInfo = [NSDictionary dictionaryWithObject:@"Reading files is not supported."
  82. forKey:NSLocalizedRecoverySuggestionErrorKey];
  83. *outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain code:0 userInfo:userInfo];
  84. }
  85. return NO;
  86. }
  87. NSURL* gitDirURL = [PBGitRepository gitDirForURL:[self fileURL]];
  88. if (!gitDirURL) {
  89. if (outError) {
  90. NSDictionary* userInfo = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"%@ does not appear to be a git repository.", [self fileName]]
  91. forKey:NSLocalizedRecoverySuggestionErrorKey];
  92. *outError = [NSError errorWithDomain:PBGitRepositoryErrorDomain code:0 userInfo:userInfo];
  93. }
  94. return NO;
  95. }
  96. [self setFileURL:gitDirURL];
  97. [self setup];
  98. return YES;
  99. }
  100. - (void) setup
  101. {
  102. config = [[PBGitConfig alloc] initWithRepositoryPath:[[self fileURL] path]];
  103. self.branches = [NSMutableArray array];
  104. [self reloadRefs];
  105. currentBranchFilter = [PBGitDefaults branchFilter];
  106. revisionList = [[PBGitHistoryList alloc] initWithRepository:self];
  107. }
  108. - (id) initWithURL: (NSURL*) path
  109. {
  110. if (![PBGitBinary path])
  111. return nil;
  112. NSURL* gitDirURL = [PBGitRepository gitDirForURL:path];
  113. if (!gitDirURL)
  114. return nil;
  115. self = [self init];
  116. [self setFileURL: gitDirURL];
  117. [self setup];
  118. // We don't want the window controller to display anything yet..
  119. // We'll leave that to the caller of this method.
  120. #ifndef CLI
  121. [self addWindowController:[[PBGitWindowController alloc] initWithRepository:self displayDefault:NO]];
  122. #endif
  123. [self showWindows];
  124. return self;
  125. }
  126. - (void) forceUpdateRevisions
  127. {
  128. [revisionList forceUpdate];
  129. }
  130. - (BOOL)isDocumentEdited
  131. {
  132. return NO;
  133. }
  134. // The fileURL the document keeps is to the .git dir, but that’s pretty
  135. // useless for display in the window title bar, so we show the directory above
  136. - (NSString *) displayName
  137. {
  138. if (![[PBGitRef refFromString:[[self headRef] simpleRef]] type])
  139. return [NSString stringWithFormat:@"%@ (detached HEAD)", [self projectName]];
  140. return [NSString stringWithFormat:@"%@ (branch: %@)", [self projectName], [[self headRef] description]];
  141. }
  142. - (NSString *) projectName
  143. {
  144. NSString *projectPath = [[self fileURL] path];
  145. if ([[projectPath lastPathComponent] isEqualToString:@".git"])
  146. projectPath = [projectPath stringByDeletingLastPathComponent];
  147. return [projectPath lastPathComponent];
  148. }
  149. // Get the .gitignore file at the root of the repository
  150. - (NSString*)gitIgnoreFilename
  151. {
  152. return [[self workingDirectory] stringByAppendingPathComponent:@".gitignore"];
  153. }
  154. - (BOOL)isBareRepository
  155. {
  156. if([self workingDirectory]) {
  157. return [PBGitRepository isBareRepository:[self workingDirectory]];
  158. } else {
  159. return true;
  160. }
  161. }
  162. // Overridden to create our custom window controller
  163. - (void)makeWindowControllers
  164. {
  165. #ifndef CLI
  166. [self addWindowController: [[PBGitWindowController alloc] initWithRepository:self displayDefault:YES]];
  167. #endif
  168. }
  169. - (PBGitWindowController *)windowController
  170. {
  171. if ([[self windowControllers] count] == 0)
  172. return NULL;
  173. return [[self windowControllers] objectAtIndex:0];
  174. }
  175. - (void) addRef: (PBGitRef *) ref fromParameters: (NSArray *) components
  176. {
  177. NSString* type = [components objectAtIndex:1];
  178. NSString* sha;
  179. if ([type isEqualToString:@"tag"] && [components count] == 4)
  180. sha = [components objectAtIndex:3];
  181. else
  182. sha = [components objectAtIndex:2];
  183. NSMutableArray* curRefs;
  184. if (curRefs = [refs objectForKey:sha])
  185. [curRefs addObject:ref];
  186. else
  187. [refs setObject:[NSMutableArray arrayWithObject:ref] forKey:sha];
  188. }
  189. - (void) reloadRefs
  190. {
  191. _headRef = nil;
  192. refs = [NSMutableDictionary dictionary];
  193. NSMutableArray *oldBranches = [branches mutableCopy];
  194. NSArray *arguments = [NSArray arrayWithObjects:@"for-each-ref", @"--format=%(refname) %(objecttype) %(objectname) %(*objectname)", @"refs", nil];
  195. NSString *output = [self outputForArguments:arguments];
  196. NSArray *lines = [output componentsSeparatedByString:@"\n"];
  197. for (NSString *line in lines) {
  198. // If its an empty line, skip it (e.g. with empty repositories)
  199. if ([line length] == 0)
  200. continue;
  201. NSArray *components = [line componentsSeparatedByString:@" "];
  202. PBGitRef *newRef = [PBGitRef refFromString:[components objectAtIndex:0]];
  203. PBGitRevSpecifier *revSpec = [[PBGitRevSpecifier alloc] initWithRef:newRef];
  204. [self addBranch:revSpec];
  205. [self addRef:newRef fromParameters:components];
  206. [oldBranches removeObject:revSpec];
  207. }
  208. for (PBGitRevSpecifier *branch in oldBranches)
  209. if ([branch isSimpleRef] && ![branch isEqual:[self headRef]])
  210. [self removeBranch:branch];
  211. [self willChangeValueForKey:@"refs"];
  212. [self didChangeValueForKey:@"refs"];
  213. [[[self windowController] window] setTitle:[self displayName]];
  214. }
  215. - (void) lazyReload
  216. {
  217. if (!hasChanged)
  218. return;
  219. [self.revisionList updateHistory];
  220. hasChanged = NO;
  221. }
  222. - (PBGitRevSpecifier *)headRef
  223. {
  224. if (_headRef)
  225. return _headRef;
  226. NSString* branch = [self parseSymbolicReference: @"HEAD"];
  227. if (branch && [branch hasPrefix:@"refs/heads/"])
  228. _headRef = [[PBGitRevSpecifier alloc] initWithRef:[PBGitRef refFromString:branch]];
  229. else
  230. _headRef = [[PBGitRevSpecifier alloc] initWithRef:[PBGitRef refFromString:@"HEAD"]];
  231. return _headRef;
  232. }
  233. - (NSString *) headSHA
  234. {
  235. return [self shaForRef:[[self headRef] ref]];
  236. }
  237. - (PBGitCommit *) headCommit
  238. {
  239. return [self commitForSHA:[self headSHA]];
  240. }
  241. - (NSString *) shaForRef:(PBGitRef *)ref
  242. {
  243. if (!ref)
  244. return nil;
  245. for (NSString *sha in refs)
  246. for (PBGitRef *existingRef in [refs objectForKey:sha])
  247. if ([existingRef isEqualToRef:ref])
  248. return sha;
  249. int retValue = 1;
  250. NSArray *args = [NSArray arrayWithObjects:@"rev-list", @"-1", [ref ref], nil];
  251. NSString *shaForRef = [self outputInWorkdirForArguments:args retValue:&retValue];
  252. if (retValue || [shaForRef isEqualToString:@""])
  253. return nil;
  254. return shaForRef;
  255. }
  256. - (PBGitCommit *) commitForRef:(PBGitRef *)ref
  257. {
  258. if (!ref)
  259. return nil;
  260. return [self commitForSHA:[self shaForRef:ref]];
  261. }
  262. - (PBGitCommit *) commitForSHA:(NSString *)sha
  263. {
  264. if (!sha)
  265. return nil;
  266. NSArray *revList = revisionList.projectCommits;
  267. for (PBGitCommit *commit in revList)
  268. if ([[commit realSha] isEqualToString:sha])
  269. return commit;
  270. return nil;
  271. }
  272. - (BOOL) isOnSameBranch:(NSString *)branchSHA asSHA:(NSString *)testSHA
  273. {
  274. if (!branchSHA || !testSHA)
  275. return NO;
  276. if ([testSHA isEqualToString:branchSHA])
  277. return YES;
  278. NSArray *revList = revisionList.projectCommits;
  279. NSMutableSet *searchSHAs = [NSMutableSet setWithObject:branchSHA];
  280. for (PBGitCommit *commit in revList) {
  281. NSString *commitSHA = [commit realSha];
  282. if ([searchSHAs containsObject:commitSHA]) {
  283. if ([testSHA isEqualToString:commitSHA])
  284. return YES;
  285. [searchSHAs removeObject:commitSHA];
  286. [searchSHAs addObjectsFromArray:commit.parents];
  287. }
  288. else if ([testSHA isEqualToString:commitSHA])
  289. return NO;
  290. }
  291. return NO;
  292. }
  293. - (BOOL) isSHAOnHeadBranch:(NSString *)testSHA
  294. {
  295. if (!testSHA)
  296. return NO;
  297. NSString *headSHA = [self headSHA];
  298. if ([testSHA isEqualToString:headSHA])
  299. return YES;
  300. return [self isOnSameBranch:headSHA asSHA:testSHA];
  301. }
  302. - (BOOL) isRefOnHeadBranch:(PBGitRef *)testRef
  303. {
  304. if (!testRef)
  305. return NO;
  306. return [self isSHAOnHeadBranch:[self shaForRef:testRef]];
  307. }
  308. - (BOOL) checkRefFormat:(NSString *)refName
  309. {
  310. int retValue = 1;
  311. [self outputInWorkdirForArguments:[NSArray arrayWithObjects:@"check-ref-format", refName, nil] retValue:&retValue];
  312. if (retValue)
  313. return NO;
  314. return YES;
  315. }
  316. - (BOOL) refExists:(PBGitRef *)ref
  317. {
  318. int retValue = 1;
  319. NSString *output = [self outputInWorkdirForArguments:[NSArray arrayWithObjects:@"for-each-ref", [ref ref], nil] retValue:&retValue];
  320. if (retValue || [output isEqualToString:@""])
  321. return NO;
  322. return YES;
  323. }
  324. // Returns either this object, or an existing, equal object
  325. - (PBGitRevSpecifier*) addBranch:(PBGitRevSpecifier*)branch
  326. {
  327. if ([[branch parameters] count] == 0)
  328. branch = [self headRef];
  329. // First check if the branch doesn't exist already
  330. for (PBGitRevSpecifier *rev in branches)
  331. if ([branch isEqual: rev])
  332. return rev;
  333. NSIndexSet *newIndex = [NSIndexSet indexSetWithIndex:[branches count]];
  334. [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:newIndex forKey:@"branches"];
  335. [branches addObject:branch];
  336. [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:newIndex forKey:@"branches"];
  337. return branch;
  338. }
  339. - (BOOL) removeBranch:(PBGitRevSpecifier *)branch
  340. {
  341. for (PBGitRevSpecifier *rev in branches) {
  342. if ([branch isEqual:rev]) {
  343. NSIndexSet *oldIndex = [NSIndexSet indexSetWithIndex:[branches indexOfObject:rev]];
  344. [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:oldIndex forKey:@"branches"];
  345. [branches removeObject:rev];
  346. [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:oldIndex forKey:@"branches"];
  347. return YES;
  348. }
  349. }
  350. return NO;
  351. }
  352. - (void) readCurrentBranch
  353. {
  354. self.currentBranch = [self addBranch: [self headRef]];
  355. }
  356. - (NSString *) workingDirectory
  357. {
  358. if ([self.fileURL.path hasSuffix:@"/.git"])
  359. return [self.fileURL.path substringToIndex:[self.fileURL.path length] - 5];
  360. else if ([[self outputForCommand:@"rev-parse --is-inside-work-tree"] isEqualToString:@"true"])
  361. return [PBGitBinary path];
  362. return nil;
  363. }
  364. #pragma mark Remotes
  365. - (NSArray *) remotes
  366. {
  367. int retValue = 1;
  368. NSString *remotes = [self outputInWorkdirForArguments:[NSArray arrayWithObject:@"remote"] retValue:&retValue];
  369. if (retValue || [remotes isEqualToString:@""])
  370. return nil;
  371. return [remotes componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
  372. }
  373. - (BOOL) hasRemotes
  374. {
  375. return ([self remotes] != nil);
  376. }
  377. - (PBGitRef *) remoteRefForBranch:(PBGitRef *)branch error:(NSError **)error
  378. {
  379. if ([branch isRemote])
  380. return [branch remoteRef];
  381. NSString *branchName = [branch branchName];
  382. if (branchName) {
  383. NSString *remoteName = [[self config] valueForKeyPath:[NSString stringWithFormat:@"branch.%@.remote", branchName]];
  384. if (remoteName && ([remoteName isKindOfClass:[NSString class]] && ![remoteName isEqualToString:@""])) {
  385. PBGitRef *remoteRef = [PBGitRef refFromString:[kGitXRemoteRefPrefix stringByAppendingString:remoteName]];
  386. // check that the remote is a valid ref and exists
  387. if ([self checkRefFormat:[remoteRef ref]] && [self refExists:remoteRef])
  388. return remoteRef;
  389. }
  390. }
  391. if (error != NULL) {
  392. NSString *info = [NSString stringWithFormat:@"There is no remote configured for the %@ '%@'.\n\nPlease select a branch from the popup menu, which has a corresponding remote tracking branch set up.\n\nYou can also use a contextual menu to choose a branch by right clicking on its label in the commit history list.", [branch refishType], [branch shortName]];
  393. *error = [NSError errorWithDomain:PBGitRepositoryErrorDomain code:0
  394. userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
  395. @"No remote configured for branch", NSLocalizedDescriptionKey,
  396. info, NSLocalizedRecoverySuggestionErrorKey,
  397. nil]];
  398. }
  399. return nil;
  400. }
  401. - (NSString *) infoForRemote:(NSString *)remoteName
  402. {
  403. int retValue = 1;
  404. NSString *output = [self outputInWorkdirForArguments:[NSArray arrayWithObjects:@"remote", @"show", remoteName, nil] retValue:&retValue];
  405. if (retValue)
  406. return nil;
  407. return output;
  408. }
  409. #pragma mark Repository commands
  410. - (void) cloneRepositoryToPath:(NSString *)path bare:(BOOL)isBare
  411. {
  412. if (!path || [path isEqualToString:@""])
  413. return;
  414. NSMutableArray *arguments = [NSMutableArray arrayWithObjects:@"clone", @"--no-hardlinks", @"--", @".", path, nil];
  415. if (isBare)
  416. [arguments insertObject:@"--bare" atIndex:1];
  417. NSString *description = [NSString stringWithFormat:@"Cloning the repository %@ to %@", [self projectName], path];
  418. NSString *title = @"Cloning Repository";
  419. [PBRemoteProgressSheet beginRemoteProgressSheetForArguments:arguments title:title description:description inRepository:self];
  420. }
  421. - (void) beginAddRemote:(NSString *)remoteName forURL:(NSString *)remoteURL
  422. {
  423. NSArray *arguments = [NSArray arrayWithObjects:@"remote", @"add", @"-f", remoteName, remoteURL, nil];
  424. NSString *description = [NSString stringWithFormat:@"Adding the remote %@ and fetching tracking branches", remoteName];
  425. NSString *title = @"Adding a remote";
  426. [PBRemoteProgressSheet beginRemoteProgressSheetForArguments:arguments title:title description:description inRepository:self];
  427. }
  428. - (void) beginFetchFromRemoteForRef:(PBGitRef *)ref
  429. {
  430. NSMutableArray *arguments = [NSMutableArray arrayWithObject:@"fetch"];
  431. if (![ref isRemote]) {
  432. NSError *error = nil;
  433. ref = [self remoteRefForBranch:ref error:&error];
  434. if (!ref) {
  435. if (error)
  436. [self.windowController showErrorSheet:error];
  437. return;
  438. }
  439. }
  440. NSString *remoteName = [ref remoteName];
  441. [arguments addObject:remoteName];
  442. NSString *description = [NSString stringWithFormat:@"Fetching all tracking branches from %@", remoteName];
  443. NSString *title = @"Fetching from remote";
  444. [PBRemoteProgressSheet beginRemoteProgressSheetForArguments:arguments title:title description:description inRepository:self];
  445. }
  446. - (void) beginPullFromRemote:(PBGitRef *)remoteRef forRef:(PBGitRef *)ref
  447. {
  448. NSMutableArray *arguments = [NSMutableArray arrayWithObject:@"pull"];
  449. // a nil remoteRef means lookup the ref's default remote
  450. if (!remoteRef || ![remoteRef isRemote]) {
  451. NSError *error = nil;
  452. remoteRef = [self remoteRefForBranch:ref error:&error];
  453. if (!remoteRef) {
  454. if (error)
  455. [self.windowController showErrorSheet:error];
  456. return;
  457. }
  458. }
  459. NSString *remoteName = [remoteRef remoteName];
  460. [arguments addObject:remoteName];
  461. NSString *branchName = nil;
  462. NSString *refSpec = nil;
  463. if ([ref isRemoteBranch]) {
  464. branchName = [ref shortName];
  465. refSpec = [ref remoteBranchName];
  466. }
  467. else if ([ref isRemote] || !ref) {
  468. branchName = @"all tracking branches";
  469. }
  470. else {
  471. branchName = [ref shortName];
  472. refSpec = [NSString stringWithFormat:@"%@:%@", branchName, branchName];
  473. }
  474. if (refSpec)
  475. [arguments addObject:refSpec];
  476. NSString *headRefName = [[[self headRef] ref] shortName];
  477. NSString *description = [NSString stringWithFormat:@"Pulling %@ from %@ and updating %@", branchName, remoteName, headRefName];
  478. NSString *title = @"Pulling from remote";
  479. [PBRemoteProgressSheet beginRemoteProgressSheetForArguments:arguments title:title description:description inRepository:self];
  480. }
  481. - (void) beginPushRef:(PBGitRef *)ref toRemote:(PBGitRef *)remoteRef
  482. {
  483. NSMutableArray *arguments = [NSMutableArray arrayWithObject:@"push"];
  484. // a nil remoteRef means lookup the ref's default remote
  485. if (!remoteRef || ![remoteRef isRemote]) {
  486. NSError *error = nil;
  487. remoteRef = [self remoteRefForBranch:ref error:&error];
  488. if (!remoteRef) {
  489. if (error)
  490. [self.windowController showErrorSheet:error];
  491. return;
  492. }
  493. }
  494. NSString *remoteName = [remoteRef remoteName];
  495. [arguments addObject:remoteName];
  496. NSString *branchName = nil;
  497. if ([ref isRemote] || !ref) {
  498. branchName = @"all updates";
  499. }
  500. else if ([ref isTag]) {
  501. branchName = [NSString stringWithFormat:@"tag '%@'", [ref tagName]];
  502. [arguments addObject:@"tag"];
  503. [arguments addObject:[ref tagName]];
  504. }
  505. else {
  506. branchName = [ref shortName];
  507. [arguments addObject:branchName];
  508. }
  509. NSString *description = [NSString stringWithFormat:@"Pushing %@ to %@", branchName, remoteName];
  510. NSString *title = @"Pushing to remote";
  511. [PBRemoteProgressSheet beginRemoteProgressSheetForArguments:arguments title:title description:description inRepository:self];
  512. }
  513. - (BOOL) checkoutRefish:(id <PBGitRefish>)ref
  514. {
  515. NSString *refName = nil;
  516. if ([ref refishType] == kGitXBranchType)
  517. refName = [ref shortName];
  518. else
  519. refName = [ref refishName];
  520. int retValue = 1;
  521. NSArray *arguments = [NSArray arrayWithObjects:@"checkout", refName, nil];
  522. NSString *output = [self outputInWorkdirForArguments:arguments retValue:&retValue];
  523. if (retValue) {
  524. NSString *message = [NSString stringWithFormat:@"There was an error checking out the %@ '%@'.\n\nPerhaps your working directory is not clean?", [ref refishType], [ref shortName]];
  525. [self.windowController showErrorSheetTitle:@"Checkout failed!" message:message arguments:arguments output:output];
  526. return NO;
  527. }
  528. [self reloadRefs];
  529. [self readCurrentBranch];
  530. return YES;
  531. }
  532. - (BOOL) checkoutFiles:(NSArray *)files fromRefish:(id <PBGitRefish>)ref
  533. {
  534. if (!files || ([files count] == 0))
  535. return NO;
  536. NSString *refName = nil;
  537. if ([ref refishType] == kGitXBranchType)
  538. refName = [ref shortName];
  539. else
  540. refName = [ref refishName];
  541. int retValue = 1;
  542. NSMutableArray *arguments = [NSMutableArray arrayWithObjects:@"checkout", refName, @"--", nil];
  543. [arguments addObjectsFromArray:files];
  544. NSString *output = [self outputInWorkdirForArguments:arguments retValue:&retValue];
  545. if (retValue) {
  546. NSString *message = [NSString stringWithFormat:@"There was an error checking out the file(s) from the %@ '%@'.\n\nPerhaps your working directory is not clean?", [ref refishType], [ref shortName]];
  547. [self.windowController showErrorSheetTitle:@"Checkout failed!" message:message arguments:arguments output:output];
  548. return NO;
  549. }
  550. return YES;
  551. }
  552. - (BOOL) mergeWithRefish:(id <PBGitRefish>)ref
  553. {
  554. NSString *refName = [ref refishName];
  555. int retValue = 1;
  556. NSArray *arguments = [NSArray arrayWithObjects:@"merge", refName, nil];
  557. NSString *output = [self outputInWorkdirForArguments:arguments retValue:&retValue];
  558. if (retValue) {
  559. NSString *headName = [[[self headRef] ref] shortName];
  560. NSString *message = [NSString stringWithFormat:@"There was an error merging %@ into %@.", refName, headName];
  561. [self.windowController showErrorSheetTitle:@"Merge failed!" message:message arguments:arguments output:output];
  562. return NO;
  563. }
  564. [self reloadRefs];
  565. [self readCurrentBranch];
  566. return YES;
  567. }
  568. - (BOOL) cherryPickRefish:(id <PBGitRefish>)ref
  569. {
  570. if (!ref)
  571. return NO;
  572. NSString *refName = [ref refishName];
  573. int retValue = 1;
  574. NSArray *arguments = [NSArray arrayWithObjects:@"cherry-pick", refName, nil];
  575. NSString *output = [self outputInWorkdirForArguments:arguments retValue:&retValue];
  576. if (retValue) {
  577. NSString *message = [NSString stringWithFormat:@"There was an error cherry picking the %@ '%@'.\n\nPerhaps your working directory is not clean?", [ref refishType], [ref shortName]];
  578. [self.windowController showErrorSheetTitle:@"Cherry pick failed!" message:message arguments:arguments output:output];
  579. return NO;
  580. }
  581. [self reloadRefs];
  582. [self readCurrentBranch];
  583. return YES;
  584. }
  585. - (BOOL) rebaseBranch:(id <PBGitRefish>)branch onRefish:(id <PBGitRefish>)upstream
  586. {
  587. if (!upstream)
  588. return NO;
  589. NSMutableArray *arguments = [NSMutableArray arrayWithObjects:@"rebase", [upstream refishName], nil];
  590. if (branch)
  591. [arguments addObject:[branch refishName]];
  592. int retValue = 1;
  593. NSString *output = [self outputInWorkdirForArguments:arguments retValue:&retValue];
  594. if (retValue) {
  595. NSString *branchName = @"HEAD";
  596. if (branch)
  597. branchName = [NSString stringWithFormat:@"%@ '%@'", [branch refishType], [branch shortName]];
  598. NSString *message = [NSString stringWithFormat:@"There was an error rebasing %@ with %@ '%@'.", branchName, [upstream refishType], [upstream shortName]];
  599. [self.windowController showErrorSheetTitle:@"Rebase failed!" message:message arguments:arguments output:output];
  600. return NO;
  601. }
  602. [self reloadRefs];
  603. [self readCurrentBranch];
  604. return YES;
  605. }
  606. - (BOOL) createBranch:(NSString *)branchName atRefish:(id <PBGitRefish>)ref
  607. {
  608. if (!branchName || !ref)
  609. return NO;
  610. int retValue = 1;
  611. NSArray *arguments = [NSArray arrayWithObjects:@"branch", branchName, [ref refishName], nil];
  612. NSString *output = [self outputInWorkdirForArguments:arguments retValue:&retValue];
  613. if (retValue) {
  614. NSString *message = [NSString stringWithFormat:@"There was an error creating the branch '%@' at %@ '%@'.", branchName, [ref refishType], [ref shortName]];
  615. [self.windowController showErrorSheetTitle:@"Create Branch failed!" message:message arguments:arguments output:output];
  616. return NO;
  617. }
  618. [self reloadRefs];
  619. return YES;
  620. }
  621. - (BOOL) createTag:(NSString *)tagName message:(NSString *)message atRefish:(id <PBGitRefish>)target
  622. {
  623. if (!tagName)
  624. return NO;
  625. NSMutableArray *arguments = [NSMutableArray arrayWithObject:@"tag"];
  626. // if there is a message then make this an annotated tag
  627. if (message && ![message isEqualToString:@""] && ([message length] > 3)) {
  628. [arguments addObject:@"-a"];
  629. [arguments addObject:[@"-m" stringByAppendingString:message]];
  630. }
  631. [arguments addObject:tagName];
  632. // if no refish then git will add it to HEAD
  633. if (target)
  634. [arguments addObject:[target refishName]];
  635. int retValue = 1;
  636. NSString *output = [self outputInWorkdirForArguments:arguments retValue:&retValue];
  637. if (retValue) {
  638. NSString *targetName = @"HEAD";
  639. if (target)
  640. targetName = [NSString stringWithFormat:@"%@ '%@'", [target refishType], [target shortName]];
  641. NSString *message = [NSString stringWithFormat:@"There was an error creating the tag '%@' at %@.", tagName, targetName];
  642. [self.windowController showErrorSheetTitle:@"Create Tag failed!" message:message arguments:arguments output:output];
  643. return NO;
  644. }
  645. [self reloadRefs];
  646. return YES;
  647. }
  648. - (BOOL) deleteRemote:(PBGitRef *)ref
  649. {
  650. if (!ref || ([ref refishType] != kGitXRemoteType))
  651. return NO;
  652. int retValue = 1;
  653. NSArray *arguments = [NSArray arrayWithObjects:@"remote", @"rm", [ref remoteName], nil];
  654. NSString * output = [self outputForArguments:arguments retValue:&retValue];
  655. if (retValue) {
  656. NSString *message = [NSString stringWithFormat:@"There was an error deleting the remote: %@\n\n", [ref remoteName]];
  657. [self.windowController showErrorSheetTitle:@"Delete remote failed!" message:message arguments:arguments output:output];
  658. return NO;
  659. }
  660. // remove the remote's branches
  661. NSString *remoteRef = [kGitXRemoteRefPrefix stringByAppendingString:[ref remoteName]];
  662. for (PBGitRevSpecifier *rev in [branches copy]) {
  663. PBGitRef *branch = [rev ref];
  664. if ([[branch ref] hasPrefix:remoteRef]) {
  665. [self removeBranch:rev];
  666. PBGitCommit *commit = [self commitForRef:branch];
  667. [commit removeRef:branch];
  668. }
  669. }
  670. [self reloadRefs];
  671. return YES;
  672. }
  673. - (BOOL) deleteRef:(PBGitRef *)ref
  674. {
  675. if (!ref)
  676. return NO;
  677. if ([ref refishType] == kGitXRemoteType)
  678. return [self deleteRemote:ref];
  679. int retValue = 1;
  680. NSArray *arguments = [NSArray arrayWithObjects:@"update-ref", @"-d", [ref ref], nil];
  681. NSString * output = [self outputForArguments:arguments retValue:&retValue];
  682. if (retValue) {
  683. NSString *message = [NSString stringWithFormat:@"There was an error deleting the ref: %@\n\n", [ref shortName]];
  684. [self.windowController showErrorSheetTitle:@"Delete ref failed!" message:message arguments:arguments output:output];
  685. return NO;
  686. }
  687. [self removeBranch:[[PBGitRevSpecifier alloc] initWithRef:ref]];
  688. PBGitCommit *commit = [self commitForRef:ref];
  689. [commit removeRef:ref];
  690. [self reloadRefs];
  691. return YES;
  692. }
  693. #pragma mark low level
  694. - (int) returnValueForCommand:(NSString *)cmd
  695. {
  696. int i;
  697. [self outputForCommand:cmd retValue: &i];
  698. return i;
  699. }
  700. - (NSFileHandle*) handleForArguments:(NSArray *)args
  701. {
  702. NSString* gitDirArg = [@"--git-dir=" stringByAppendingString:self.fileURL.path];
  703. NSMutableArray* arguments = [NSMutableArray arrayWithObject: gitDirArg];
  704. [arguments addObjectsFromArray: args];
  705. return [PBEasyPipe handleForCommand:[PBGitBinary path] withArgs:arguments];
  706. }
  707. - (NSFileHandle*) handleInWorkDirForArguments:(NSArray *)args
  708. {
  709. NSString* gitDirArg = [@"--git-dir=" stringByAppendingString:self.fileURL.path];
  710. NSMutableArray* arguments = [NSMutableArray arrayWithObject: gitDirArg];
  711. [arguments addObjectsFromArray: args];
  712. return [PBEasyPipe handleForCommand:[PBGitBinary path] withArgs:arguments inDir:[self workingDirectory]];
  713. }
  714. - (NSFileHandle*) handleForCommand:(NSString *)cmd
  715. {
  716. NSArray* arguments = [cmd componentsSeparatedByString:@" "];
  717. return [self handleForArguments:arguments];
  718. }
  719. - (NSString*) outputForCommand:(NSString *)cmd
  720. {
  721. NSArray* arguments = [cmd componentsSeparatedByString:@" "];
  722. return [self outputForArguments: arguments];
  723. }
  724. - (NSString*) outputForCommand:(NSString *)str retValue:(int *)ret;
  725. {
  726. NSArray* arguments = [str componentsSeparatedByString:@" "];
  727. return [self outputForArguments: arguments retValue: ret];
  728. }
  729. - (NSString*) outputForArguments:(NSArray*) arguments
  730. {
  731. return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir: self.fileURL.path];
  732. }
  733. - (NSString*) outputInWorkdirForArguments:(NSArray*) arguments
  734. {
  735. return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir: [self workingDirectory]];
  736. }
  737. - (NSString*) outputInWorkdirForArguments:(NSArray *)arguments retValue:(int *)ret
  738. {
  739. return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir:[self workingDirectory] retValue: ret];
  740. }
  741. - (NSString*) outputForArguments:(NSArray *)arguments retValue:(int *)ret
  742. {
  743. return [PBEasyPipe outputForCommand:[PBGitBinary path] withArgs:arguments inDir: self.fileURL.path retValue: ret];
  744. }
  745. - (NSString*) outputForArguments:(NSArray *)arguments inputString:(NSString *)input retValue:(int *)ret
  746. {
  747. return [PBEasyPipe outputForCommand:[PBGitBinary path]
  748. withArgs:arguments
  749. inDir:[self workingDirectory]
  750. inputString:input
  751. retValue: ret];
  752. }
  753. - (NSString *)outputForArguments:(NSArray *)arguments inputString:(NSString *)input byExtendingEnvironment:(NSDictionary *)dict retValue:(int *)ret
  754. {
  755. return [PBEasyPipe outputForCommand:[PBGitBinary path]
  756. withArgs:arguments
  757. inDir:[self workingDirectory]
  758. byExtendingEnvironment:dict
  759. inputString:input
  760. retValue: ret];
  761. }
  762. - (BOOL)executeHook:(NSString *)name output:(NSString **)output
  763. {
  764. return [self executeHook:name withArgs:[NSArray array] output:output];
  765. }
  766. - (BOOL)executeHook:(NSString *)name withArgs:(NSArray *)arguments output:(NSString **)output
  767. {
  768. NSString *hookPath = [[[[self fileURL] path] stringByAppendingPathComponent:@"hooks"] stringByAppendingPathComponent:name];
  769. if (![[NSFileManager defaultManager] isExecutableFileAtPath:hookPath])
  770. return TRUE;
  771. NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
  772. [self fileURL].path, @"GIT_DIR",
  773. [[self fileURL].path stringByAppendingPathComponent:@"index"], @"GIT_INDEX_FILE",
  774. nil
  775. ];
  776. int ret = 1;
  777. NSString *_output = [PBEasyPipe outputForCommand:hookPath withArgs:arguments inDir:[self workingDirectory] byExtendingEnvironment:info inputString:nil retValue:&ret];
  778. if (output)
  779. *output = _output;
  780. return ret == 0;
  781. }
  782. - (NSString *)parseReference:(NSString *)reference
  783. {
  784. int ret = 1;
  785. NSString *ref = [self outputForArguments:[NSArray arrayWithObjects: @"rev-parse", @"--verify", reference, nil] retValue: &ret];
  786. if (ret)
  787. return nil;
  788. return ref;
  789. }
  790. - (NSString*) parseSymbolicReference:(NSString*) reference
  791. {
  792. NSString* ref = [self outputForArguments:[NSArray arrayWithObjects: @"symbolic-ref", @"-q", reference, nil]];
  793. if ([ref hasPrefix:@"refs/"])
  794. return ref;
  795. return nil;
  796. }
  797. - (void) finalize
  798. {
  799. NSLog(@"Dealloc of repository");
  800. [super finalize];
  801. }
  802. @end