forked from dgrijalva/gitx
-
Notifications
You must be signed in to change notification settings - Fork 76
/
PBGitCommitController.m
297 lines (237 loc) · 10.4 KB
/
PBGitCommitController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
//
// PBGitCommitController.m
// GitX
//
// Created by Pieter de Bie on 19-09-08.
// Copyright 2008 __MyCompanyName__. All rights reserved.
//
#import "PBGitCommitController.h"
#import "NSFileHandleExt.h"
#import "PBChangedFile.h"
#import "PBWebChangesController.h"
#import "PBGitIndex.h"
#define kCommitSplitViewPositionDefault @"Commit SplitView Position"
@interface PBGitCommitController ()
- (void)refreshFinished:(NSNotification *)notification;
- (void)commitWithVerification:(BOOL) doVerify;
- (void)commitStatusUpdated:(NSNotification *)notification;
- (void)commitFinished:(NSNotification *)notification;
- (void)commitFailed:(NSNotification *)notification;
- (void)commitHookFailed:(NSNotification *)notification;
- (void)amendCommit:(NSNotification *)notification;
- (void)indexChanged:(NSNotification *)notification;
- (void)indexOperationFailed:(NSNotification *)notification;
- (void)saveCommitSplitViewPosition;
@end
@implementation PBGitCommitController
@synthesize index;
- (id)initWithRepository:(PBGitRepository *)theRepository superController:(PBGitWindowController *)controller
{
if (!(self = [super initWithRepository:theRepository superController:controller]))
return nil;
index = [[PBGitIndex alloc] initWithRepository:theRepository workingDirectory:[NSURL fileURLWithPath:[theRepository workingDirectory]]];
[index refresh];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshFinished:) name:PBGitIndexFinishedIndexRefresh object:index];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(commitStatusUpdated:) name:PBGitIndexCommitStatus object:index];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(commitFinished:) name:PBGitIndexFinishedCommit object:index];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(commitFailed:) name:PBGitIndexCommitFailed object:index];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(commitHookFailed:) name:PBGitIndexCommitHookFailed object:index];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(amendCommit:) name:PBGitIndexAmendMessageAvailable object:index];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(indexChanged:) name:PBGitIndexIndexUpdated object:index];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(indexOperationFailed:) name:PBGitIndexOperationFailed object:index];
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
[commitMessageView setTypingAttributes:[NSDictionary dictionaryWithObject:[NSFont fontWithName:@"Monaco" size:12.0] forKey:NSFontAttributeName]];
[unstagedFilesController setFilterPredicate:[NSPredicate predicateWithFormat:@"hasUnstagedChanges == 1"]];
[cachedFilesController setFilterPredicate:[NSPredicate predicateWithFormat:@"hasStagedChanges == 1"]];
[unstagedFilesController setSortDescriptors:[NSArray arrayWithObjects:
[[NSSortDescriptor alloc] initWithKey:@"status" ascending:false],
[[NSSortDescriptor alloc] initWithKey:@"path" ascending:true], nil]];
[cachedFilesController setSortDescriptors:[NSArray arrayWithObject:
[[NSSortDescriptor alloc] initWithKey:@"path" ascending:true]]];
[cachedFilesController setAutomaticallyRearrangesObjects:NO];
[unstagedFilesController setAutomaticallyRearrangesObjects:NO];
[commitSplitView setHidden:YES];
[self performSelector:@selector(restoreCommitSplitViewPositiion) withObject:nil afterDelay:0];
}
- (void)closeView
{
[self saveCommitSplitViewPosition];
[webController closeView];
}
- (NSResponder *)firstResponder;
{
return commitMessageView;
}
- (IBAction)signOff:(id)sender
{
if (![repository.config valueForKeyPath:@"user.name"] || ![repository.config valueForKeyPath:@"user.email"])
return [[repository windowController] showMessageSheet:@"User's name not set" infoText:@"Signing off a commit requires setting user.name and user.email in your git config"];
NSString *SOBline = [NSString stringWithFormat:@"Signed-off-by: %@ <%@>",
[repository.config valueForKeyPath:@"user.name"],
[repository.config valueForKeyPath:@"user.email"]];
if([commitMessageView.string rangeOfString:SOBline].location == NSNotFound) {
NSArray *selectedRanges = [commitMessageView selectedRanges];
commitMessageView.string = [NSString stringWithFormat:@"%@\n\n%@",
commitMessageView.string, SOBline];
[commitMessageView setSelectedRanges: selectedRanges];
}
}
- (void) refresh:(id) sender
{
self.isBusy = YES;
self.status = @"Refreshing index…";
[index refresh];
// Reload refs (in case HEAD changed)
[repository reloadRefs];
}
- (void) updateView
{
[self refresh:nil];
}
- (IBAction) commit:(id) sender
{
[self commitWithVerification:YES];
}
- (IBAction) forceCommit:(id) sender
{
[self commitWithVerification:NO];
}
- (void) commitWithVerification:(BOOL) doVerify
{
if ([[cachedFilesController arrangedObjects] count] == 0) {
[[repository windowController] showMessageSheet:@"No changes to commit" infoText:@"You must first stage some changes before committing"];
return;
}
NSString *commitMessage = [commitMessageView string];
if ([commitMessage length] < 3) {
[[repository windowController] showMessageSheet:@"Commit message missing" infoText:@"Please enter a commit message before committing"];
return;
}
if ([[NSFileManager defaultManager] fileExistsAtPath:[repository.fileURL.path stringByAppendingPathComponent:@"MERGE_HEAD"]]) {
[index commitMergeWithMessage:commitMessage];
return;
}
[cachedFilesController setSelectionIndexes:[NSIndexSet indexSet]];
[unstagedFilesController setSelectionIndexes:[NSIndexSet indexSet]];
self.isBusy = YES;
[commitMessageView setEditable:NO];
[index commitWithMessage:commitMessage andVerify:doVerify];
}
# pragma mark PBGitIndex Notification handling
- (void)refreshFinished:(NSNotification *)notification
{
self.isBusy = NO;
self.status = @"Index refresh finished";
}
- (void)commitStatusUpdated:(NSNotification *)notification
{
self.status = [[notification userInfo] objectForKey:@"description"];
}
- (void)commitFinished:(NSNotification *)notification
{
[commitMessageView setEditable:YES];
[commitMessageView setString:@""];
[webController setStateMessage:[NSString stringWithString:[[notification userInfo] objectForKey:@"description"]]];
[repository reloadRefs];
}
- (void)commitFailed:(NSNotification *)notification
{
self.isBusy = NO;
NSString *reason = [[notification userInfo] objectForKey:@"description"];
self.status = [@"Commit failed: " stringByAppendingString:reason];
[commitMessageView setEditable:YES];
[[repository windowController] showMessageSheet:@"Commit failed" infoText:reason];
}
- (void)commitHookFailed:(NSNotification *)notification
{
self.isBusy = NO;
NSString *reason = [[notification userInfo] objectForKey:@"description"];
self.status = [@"Commit hook failed: " stringByAppendingString:reason];
[commitMessageView setEditable:YES];
[[repository windowController] showCommitHookFailedSheet:@"Commit hook failed" infoText:reason commitController:self];
}
- (void)amendCommit:(NSNotification *)notification
{
// Replace commit message with the old one if it's less than 3 characters long.
// This is just a random number.
if ([[commitMessageView string] length] > 3)
return;
NSString *message = [[notification userInfo] objectForKey:@"message"];
commitMessageView.string = message;
}
- (void)indexChanged:(NSNotification *)notification
{
[cachedFilesController rearrangeObjects];
[unstagedFilesController rearrangeObjects];
if ([[cachedFilesController arrangedObjects] count]) {
[commitButton setEnabled:YES];
} else {
[commitButton setEnabled:NO];
}
}
- (void)indexOperationFailed:(NSNotification *)notification
{
[[repository windowController] showMessageSheet:@"Index operation failed" infoText:[[notification userInfo] objectForKey:@"description"]];
}
#pragma mark NSSplitView delegate methods
#define kCommitSplitViewTopViewMin 150
#define kCommitSplitViewBottomViewMin 100
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex
{
if (proposedMin < kCommitSplitViewTopViewMin)
return kCommitSplitViewTopViewMin;
return proposedMin;
}
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex
{
CGFloat max=[splitView frame].size.height - [splitView dividerThickness] - kCommitSplitViewBottomViewMin;
if (max < proposedMax)
return max;
return proposedMax;
}
// while the user resizes the window keep the lower (changes/message) view constant and just resize the upper view
// unless the upper view gets too small
- (void)resizeCommitSplitView
{
NSRect newFrame = [commitSplitView frame];
float dividerThickness = [commitSplitView dividerThickness];
NSView *upperView = [[commitSplitView subviews] objectAtIndex:0];
NSRect upperFrame = [upperView frame];
upperFrame.size.width = newFrame.size.width;
NSView *lowerView = [[commitSplitView subviews] objectAtIndex:1];
NSRect lowerFrame = [lowerView frame];
lowerFrame.size.width = newFrame.size.width;
upperFrame.size.height = newFrame.size.height - lowerFrame.size.height - dividerThickness;
if (upperFrame.size.height < kCommitSplitViewTopViewMin)
upperFrame.size.height = kCommitSplitViewTopViewMin;
lowerFrame.size.height = newFrame.size.height - upperFrame.size.height - dividerThickness;
lowerFrame.origin.y = newFrame.size.height - lowerFrame.size.height;
[upperView setFrame:upperFrame];
[lowerView setFrame:lowerFrame];
}
- (void)splitView:(NSSplitView *)splitView resizeSubviewsWithOldSize:(NSSize)oldSize
{
if (splitView == commitSplitView)
[self resizeCommitSplitView];
}
// NSSplitView does not save and restore the position of the splitView correctly so do it manually
- (void)saveCommitSplitViewPosition
{
float position = [[[commitSplitView subviews] objectAtIndex:0] frame].size.height;
[[NSUserDefaults standardUserDefaults] setFloat:position forKey:kCommitSplitViewPositionDefault];
[[NSUserDefaults standardUserDefaults] synchronize];
}
// make sure this happens after awakeFromNib
- (void)restoreCommitSplitViewPositiion
{
float position = [[NSUserDefaults standardUserDefaults] floatForKey:kCommitSplitViewPositionDefault];
if (position < 1.0)
position = [commitSplitView frame].size.height - 225;
[commitSplitView setPosition:position ofDividerAtIndex:0];
[commitSplitView setHidden:NO];
}
@end