最近公司的代码仓库由gitlab切换到了gerrit,推送代码不能直接使用sourcetree自带的推送按钮了。看了下网上基本都是基于自定义操作来执行以下脚本,的确好使,不过就是每次操作有点累。
#!/bin/sh
# 获取当前分支名
branch=`git symbolic-ref --short -q HEAD`
# push review
git push origin HEAD:refs/for/${branch}
找到个大佬写的文章,好当给力。
在Sourcetree中的review code仓库页面中的Toolbar上加入几个按钮,替代CustomAction中的快捷键
还是放不下心爱的sourcetree,让我们来看看sourcetree的推送跟脚本的推送差别到底在哪里。
一般仓库推送时,是这样的,
git push origin refs/heads/main:refs/heads/main
可以看到sourcetree也是这样的推送
而gerrit推送时是这样的
git push origin HEAD:refs/for/main
如果能在sourcetree里替换下最后个参数就行。
让我直接上hook代码
#import "TestMac.h"
#import "Aspects.h"
@import Cocoa;
@interface TestMac()
@property(nonatomic, copy) NSString *repoPath;
@end
@implementation TestMac
static TestMac *testMacObj;
+(void)load {
NSLog(@"插件注入成功");
testMacObj = [TestMac new];
hookSTRepoWindowControllerWindowDidLoad();
hookSTTaskInitWithCommand();
}
void hookSTRepoWindowControllerWindowDidLoad(void) {
[NSClassFromString(@"STRepoWindowController") aspect_hookSelector:@selector(windowDidLoad) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo){
NSString *repoPath = [aspectInfo.instance performSelector:@selector(repoPath)];
if ([repoPath isKindOfClass:[NSString class]] && repoPath.length > 0) {
// 这里我们存下仓库路径
testMacObj.repoPath = repoPath;
}
} error: nil];
}
void hookSTTaskInitWithCommand(void) {
NSError *error = nil;
[NSClassFromString(@"STTask") aspect_hookSelector:@selector(initWithCommand:workingDir:args:)
withOptions:AspectPositionInstead
usingBlock:^(id<AspectInfo> aspectInfo, NSString *command, NSString *workingDir, NSArray *args) {
NSString *msgFilePath = [NSString stringWithFormat:@"%@/%@", testMacObj.repoPath, @".git/hooks/commit-msg"];
BOOL isGerrit = [[NSFileManager defaultManager] fileExistsAtPath:msgFilePath];
// 这里可以判断.git/hooks/commit-msg文件存在不
if (!isGerrit) {
[aspectInfo.originalInvocation invoke];
return;
}
BOOL showChange = NO;
NSString *lastCMD = args.lastObject;
NSString *branchName = @"";
if ([lastCMD isKindOfClass:[NSString class]]) {
if ([lastCMD hasPrefix:@"refs/heads"]) {
showChange = YES;
NSArray *components = [[[lastCMD componentsSeparatedByString:@":"] firstObject] componentsSeparatedByString:@"/"];
branchName = components.lastObject;
}
} else {
NSLog(@"类型不是字符串: %@", NSStringFromClass([lastCMD class]));
}
if (showChange && branchName.length > 0) {
NSMutableArray *mutableArgs = [args mutableCopy];
if (mutableArgs.count > 0) {
// 这里我们可以直接添加上要指令review的人邮箱,不然每次都要在网页上选
mutableArgs[mutableArgs.count - 1] = [NSString stringWithFormat:@"HEAD:refs/for/%@%@", branchName, @"%r=xing@run.com,r=xing@test.com"];
}
id<AspectInfo> strongAspectInfo = aspectInfo;
SEL selector = aspectInfo.originalInvocation.selector;
NSInvocation *invocation = strongAspectInfo.originalInvocation;
[invocation setSelector:selector];
[invocation setArgument:&mutableArgs atIndex:4];
[invocation invoke];
} else {
[aspectInfo.originalInvocation invoke];
}
} error:&error];
if (error) {
NSLog(@"hook STTask initWithCommand 错误: %@", error);
}
}
@end
大概思路是:在STRepoWindowController的windowDidLoad时我们需要获取到仓库的路径。点推送时,我们判断下这个仓库是不是gerrit的,可以判断.git/hooks/下的目标文件存在不,也可以判断url包含gerrit等。如果是就把STTask的args中的最后个参数换成目标字符串,如果不是还是走之前的逻辑。至此已经无感使用了
MonkeyDev创建的mac工程在m1上老是报错,就重新创建了个工程用的Aspects来hook,感觉还是挺好用的,dylib注入用的optool,也还能正常使用。
有时不能用修改二进制的方式注入,可以换个方式如
#!/bin/bash
DYLD_INSERT_LIBRARIES=$SCRIPT_DIR/TestMac/libTestMac.dylib /Applications/Sourcetree.app/Contents/MacOS/Sourcetree
关于定位到类的方法用的frida
# 跟踪方法
frida-trace -m "+[STTaskWindowController *]" "Sourcetree"
frida-trace -m "-[*STTask* *beginTasks*]" "Sourcetree"
#忽略 class
frida-trace -m "*[STTaskWindowController *]" "Sourcetree" -M "*[STTaskWindowController class]"