无感顺滑地使用Sourcetree推送code review 到gerrit

最近代码仓库由gitlab切换到了gerrit,推送代码不能直接使用sourcetree自带的推送按钮了。看了下网上基本都是基于自定义操作来执行以下脚本,的确好使,不过就是每次操作有点累。

sh 复制代码
#!/bin/sh
# 获取当前分支名
branch=`git symbolic-ref --short -q HEAD`
# push review
git push origin HEAD:refs/for/${branch}

找到个大佬写的文章,好当给力。

在Sourcetree中的review code仓库页面中的Toolbar上加入几个按钮,替代CustomAction中的快捷键

基于此,我有一些新的想法:

1.改造终端事件:我想在点终端时,如果是gerrit仓库就再追加执行脚本命令。

2.改造推送事件,如果是gerrit仓库就使用新命令。

一般仓库推送时,是这样的

sh 复制代码
git push origin refs/heads/main:refs/heads/main

而gerrit推送时是这样的

sh 复制代码
git push origin HEAD:refs/for/main

只要替换下最后个参数就行。 让我直接上hook代码

objc 复制代码
#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();
    hookNSToolbarItemSetAction(testMacObj);
    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 hookNSToolbarItemSetAction(TestMac *obj) {
    NSError *error = nil;
    [NSToolbarItem aspect_hookSelector:@selector(setAction:)
                           withOptions:AspectPositionInstead
                            usingBlock:^(id<AspectInfo> aspectInfo, SEL action) {

        NSToolbarItem *toolbarItem = aspectInfo.instance;
        if ([NSStringFromSelector(action) containsString:@"openTerminal"] && ![toolbarItem.target isEqualTo:obj]) {

            // 这里我们重置打开终端的target和action
            toolbarItem.target = obj;
            toolbarItem.action = @selector(openTerminal:);

        } else {

            [aspectInfo.originalInvocation invoke];
        }

    } error:&error];

    if (error) {
        NSLog(@"Error hookNSToolbarItemSetAction: %@", error);
    }

}

- (void)openTerminal:(id)fff {
    NSString *msgFilePath = [NSString stringWithFormat:@"%@/%@", testMacObj.repoPath, @".git/hooks/commit-msg"];
    BOOL isGerrit = [[NSFileManager defaultManager] fileExistsAtPath:msgFilePath];
    NSString *command = [NSString stringWithFormat:@"cd %@;",  testMacObj.repoPath];

    if (isGerrit) {
        NSString *shellScriptPath = @"~/gerritPush.sh";
        command = [NSString stringWithFormat:@"%@ sh %@",  command, shellScriptPath];
    }

    NSString *scriptSource = [NSString stringWithFormat:@"tell application \"Terminal\"\n"

                              "    activate\n"

                              "    delay 0.25\n"

                              "    do script \"%@\"\n"

                              "end tell\n", command];

    

    NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:scriptSource];

    
    NSDictionary *errorInfo = nil;
    NSAppleEventDescriptor *result = [appleScript executeAndReturnError:&errorInfo];

    if (!result) {
        showMsg([errorInfo objectForKey:NSAppleScriptErrorMessage]);
        return;
    }
}

  


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];

         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) {

                mutableArgs[mutableArgs.count - 1] = [NSString stringWithFormat:@"HEAD:refs/for/%@", branchName];

            }

            

            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);
    }
}

void showMsg(NSString *msg) {
    NSAlert *alert = [[NSAlert alloc] init];
    alert.messageText = @"日志";
    alert.informativeText =  msg;
    [alert addButtonWithTitle:@"OK"];
    [alert beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow] completionHandler:nil];
}
@end

大概思路是:在STRepoWindowControllerwindowDidLoad时我们需要获取到仓库的路径,然后我们重置打开终端NSToolbarItem的的targetaction。点推送时,我们重新设置下最后个参数值就是如果是gerrit就替换下目标字符串就是。MonkeyDev创建的mac工程在m1上老是报错,就重新创建了个工程用的Aspects来hook,感觉还是挺好用的,dylib注入用的optool,也还能正常使用。

相关推荐
kyriewen19 分钟前
我排查了一个React内存泄漏——罪魁祸首是这3个被忽略的清理函数
前端·javascript·面试
IT_陈寒26 分钟前
我又被JavaScript的隐式类型转换坑了
前端·人工智能·后端
其美杰布-富贵-李1 小时前
03 ref、reactive 与 computed 响应式数据
前端·javascript·vue.js
OpenTiny社区1 小时前
GenUI SDK v1.3.0 发布|多框架兼容,一键换物料,渲染器 & 演练场全面增强!
前端·ai编程
用户938515635071 小时前
useRef + Web Worker 实战:React 如何优雅地拥抱多线程
前端·javascript·react.js
他们叫我秃子1 小时前
前端开发转 Go 全栈(五):终于遇到熟人了,Go 的闭包和高阶函数原来这么像 JavaScript
前端·后端·go
用户61595868000221 小时前
从 0 写一个迷你 wujie:用原生 Web API 实现微前端
前端
妙码生花1 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(五十七):AI时代对组件封装的新理解、agInput 统一入口,动态表单预备
前端·后端·go
fairyly1 小时前
接手新项目不用硬啃源码,我的 TRAE Work 快速上手实战工作流
前端·ai编程·trae
搬砖记录员1 小时前
用 PyAutoGUI 手搓录屏"老板键":从原理到四套可跑源码,附工程化落地思考
前端