今天我们讲讲gerrit 的 ssh 命令的 ls-user-refs命令的实现的实现,采用的代码是gerrit的 stable2.15 分支的,因为我们使用的版本是 2.15.1的。
LsUserRefs 类详解 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 这个命令在 com.google.gerrit.sshd.commands.LsUserRefs 类中, @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) @CommandMetaData( name = "ls-user-refs", description = "List refs visible to a specific user", runsAt = MASTER_OR_SLAVE) public class LsUserRefs extends SshCommand { @Inject private AccountResolver accountResolver; @Inject private OneOffRequestContext requestContext; @Inject private VisibleRefFilter.Factory refFilterFactory; @Inject private GitRepositoryManager repoManager; @Option( name = "--project", aliases = {"-p"}, metaVar = "PROJECT", required = true, usage = "project for which the refs should be listed") private ProjectControl projectControl; @Option( name = "--user", aliases = {"-u"}, metaVar = "USER", required = true, usage = "user for which the groups should be listed") private String userName; @Option(name = "--only-refs-heads", usage = "list only refs under refs/heads") private boolean onlyRefsHeads; @Override protected void run() throws Failure { Account userAccount; try { userAccount = accountResolver.find(userName); } catch (OrmException | IOException | ConfigInvalidException e) { throw die(e); } if (userAccount == null) { stdout.print("No single user could be found when searching for: " + userName + '\n'); stdout.flush(); return; } Project.NameKey projectName = projectControl.getProject().getNameKey(); try (Repository repo = repoManager.openRepository(projectName); ManualRequestContext ctx = requestContext.openAs(userAccount.getId())) { try { Map<String, Ref> refsMap = refFilterFactory .create(projectControl.getProjectState(), repo) .filter(repo.getRefDatabase().getRefs(ALL), false); for (String ref : refsMap.keySet()) { if (!onlyRefsHeads || ref.startsWith(RefNames.REFS_HEADS)) { stdout.println(ref); } } } catch (IOException e) { throw new Failure(1, "fatal: Error reading refs: '" + projectName, e); } } catch (RepositoryNotFoundException e) { throw die("'" + projectName + "': not a git archive"); } catch (IOException | OrmException e) { throw die("Error opening: '" + projectName); } } }
注解@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) 这个暂时没理解, 估计是 需要什么 能力, 也就是 Requires Capability字面翻译, 需要一个全局的 能力GlobalCapability, 感觉像是 需要什么权限类似的,就是说执行这个 ls-user-refs命令 命令是需要一定权限的。 下面我们看看 com.google.gerrit.common.data.GlobalCapability 类里面的, 可以发现 里面确实是一个一个的权限类型。 /** Server wide capabilities. Represented as {@link Permission} objects. */ public class GlobalCapability { /** Ability to access the database (with gsql). */ public static final String ACCESS_DATABASE = "accessDatabase"; /** * Denotes the server's administrators. * * <p>This is similar to UNIX root, or Windows SYSTEM account. Any user that has this capability * can perform almost any other action, or can grant themselves the power to perform any other * action on the site. Most of the other capabilities and permissions fall-back to the predicate * "OR user has capability ADMINISTRATE_SERVER". */ public static final String ADMINISTRATE_SERVER = "administrateServer"; GlobalCapability.ADMINISTRATE_SERVER 就是管理员。 @RequiresCapability 这个注解 放这里就是说明 执行 ls-user-refs命令 需要 administrateServer 权限。
1 2 3 4 5 6 7 @CommandMetaData( name = "ls-user-refs", description = "List refs visible to a specific user", runsAt = MASTER_OR_SLAVE) 这个是 定义 这个类是要执行什么命令的, name 就是命令名称了。 description 命令的描述,runsAt 表示这个命令可以运行在master端 还是 slave端。(gerrit也有 master - slave的模式的。有些命令是在slave模式下面没有的。)
LsUserRefs extends SshCommand 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class LsUserRefs extends SshCommand { @Inject private AccountResolver accountResolver; @Inject private OneOffRequestContext requestContext; @Inject private VisibleRefFilter.Factory refFilterFactory; @Inject private GitRepositoryManager repoManager; 从这个 extends 继承关系可以发现 所有的 ssh -p 29418 gerrit.example.com gerrit <命令> 这样类似的命令估计都会 继承 SshCommand 类。 一般的类似spring IOC这样的容器,在服务启动之后 会扫描所有的 子类的实现, 例如扫描所有 SshCommand 子类, 然后就可以得到所有的ssh命令行的实现,然后在自动的依赖注入,下面我们就会看到。 又比如,jenkins中,很多类都会实现 JobProperty ,(比如这个插件 public class BuildLogCompressor extends JobProperty<AbstractProject<?, ?>>), 这样jenkins在启动的时候会 扫描所有的 实现JobProperty类。
@Inject依赖注入 1 2 3 4 5 6 7 8 9 10 public class LsUserRefs extends SshCommand { @Inject private AccountResolver accountResolver; @Inject private OneOffRequestContext requestContext; @Inject private VisibleRefFilter.Factory refFilterFactory; @Inject private GitRepositoryManager repoManager; 从这里 @Inject 我们可以看到 这里有4个成员, 这几个的实例化估计都是 通过 @Inject ,或者说是某个框架自动注入的。 gerrit应该用的不是我们平常属性的spring框架,是google自己开发的一个 Guice - 轻量级IoC容器,Google公司开发的轻量级IoC容器,其特点是: 速度快,据说是spring的100倍速度;无需配置文件。
@Option 注解 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 @Option( name = "--project", aliases = {"-p"}, metaVar = "PROJECT", required = true, usage = "project for which the refs should be listed") private ProjectControl projectControl; @Option( name = "--user", aliases = {"-u"}, metaVar = "USER", required = true, usage = "user for which the groups should be listed") private String userName; @Option(name = "--only-refs-heads", usage = "list only refs under refs/heads") private boolean onlyRefsHeads; 先看 private ProjectControl projectControl; 这个注解 应该是 设置命令 需要的选项,然后把 命令行参数 选项后面的值 初始化到对应的类成员变量中, 例如 --project 这个选项,如果命令行上 填写了, --project git/android/repo 这样的, gerrit就会初始 projectControl 这个, 这个变量是个复杂的类型, 不过这个类 ProjectControl 里面肯定有一个属性 会存放 git/android/repo 这个值的。不过通过看 ProjectControl源码发现, 并没有这么简单,里面嵌入了好几层。 不过通过字面意思和命令参数的作用,可以猜出 这个 projectControl 就是存放 git 仓库路径的, 就是gerrit上的project的。 例如命令 create-branch 中有这个 project的概念。 下面是 create-branch 中的 com.google.gerrit.sshd.commands.CreateBranchCommand @Argument(index = 0, required = true, metaVar = "PROJECT", usage = "name of the project") private ProjectControl project; 这个 ProjectControl 属性,和我们猜测的一样。 name = "--project", 指定选项名称, aliases = {"-p"}, 指定了选项别名,这里是一个短选项,例如很多linux中的命令 都有 长选项 和 短选项的。 metaVar = "PROJECT", 说明选项后面需要跟上一个值的。 required = true, 表面这个选项是必须的。有些选项是可选的,就是可以不用显示的指出,这些应该是会有一个默认值。 但是像这个 如果不执行,怎么知道你要查看哪个仓库下面的 refs 是否有权限呢??? 要不然所有仓库都列出来?,这样就会太多了。 一个命令 尽可能的做到功能单一, 一个命令再和其他命令组合起来实现一个复杂的命令。就比如这个想列出来所有仓库的, 可以先用 ls-projects 命令列出所有仓库,然后再结合 这个 ls-user-refs 命令。这就是unix编程艺术 思想。 usage = "project for which the refs should be listed" 这个说明了这个选项的作用,描述等等。 再看另外一个 private String userName; 这个应该是 用户名,就是你想列出 哪个用的 对这个仓库的 最后看 private boolean onlyRefsHeads; 这是一个布尔类型的,如果命令行有个 --only-refs-heads 选项,就只会 列出 refs/heads/ 下面的。也就是之后列出分支了。 如果没有就会 列出 refs/heads/ 和 refs/tags/ 下面的。 这个注解 其实是来自jenkins的。哈哈哈。 import org.kohsuke.args4j.Option;
run() 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override protected void run() throws Failure { 最后我们看这个 run() 方法,这个很类似 java 中Thread的用法,里面放到就是命令主要实现。 public abstract class SshCommand extends BaseCommand { SshCommand 继承 BaseCommand 类,估计出了 ssh 命令,还会有其他的命令类型吧。 例如 HttpCommand??? GitCommand???这里只是猜测。。 public abstract class BaseCommand implements Command { BaseCommand 又实现了 接口 Command, 原来是 org.apache.sshd.server.Command 这个接口,也就是 apache 的ssh命令实现。 我们猜测的 真对,还真有个 gitcommand,估计是执行git相关的命令的。 public abstract class AbstractGitCommand extends BaseCommand 从run() 方法中我们可以看到 仓库路径 project 是怎么用的: Project.NameKey projectName = projectControl.getProject().getNameKey(); 仓库名,仓库路径是存放为 Project.NameKey 类型的。。。。。projectControl里面包装了好几层。。。 有了git仓库 然后怎么使用呢,调用 repoManager 来 处理 git仓库: try (Repository repo = repoManager.openRepository(projectName);
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 from https://blog.csdn.net/czq7511/article/details/73610510 本文章主要记录本人在学习开发Gerrit插件过程中的一些心得,一些零零碎碎的资料: 一、关于插件jar的META-INF/MANIFEST.MF文件:这个文件的主要作用是记录一些有关这个jar的属性,以便被识别、引用或加载,也可以理解为是这个jar的配置文件。 这里举些例子: Manifest-Version: 1.0----配置版本号。如果日后要做升级兼容的话,可以利用这个属性。 Gerrit-ApiType: plugin----指定Gerrit插件类型为plugin。Gerrit插件类型有两种:plugin和extension Gerrit-ApiVersion: 2.13.7----指定该插件匹配的Gerrit平台版本,如果指定版本不一致的话,插件很有可能不能用。 Implementation-Title: Cookbook plugin ----实现Titile Implementation-Version: 2.13.7----实现的版本号,也就是该插件的版本号,一般和要适应的Gerrit平台的版本号一致。 Archiver-Version: Plexus Archiver---- Built-By: czq ----打包作者 Gerrit-PluginName: cookbook----插件名称 Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module----Gerrit加载插件时,会根据这个指定去加载相应的Module类。该类主要实现前后台业务逻辑。 Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule ----Gerrit加载插件时,会根据这个指定去加载相应的HttpModule类。该类主要实现Http接口业务逻辑。 Gerrit-SshModule: com.googlesource.gerrit.plugins.cookbook.SshModule ----Gerrit加载插件时,会根据这个指定去加载相应的HttpModule类。该类主要实现SSH接口业务逻辑。 Implementation-Vendor: Gerrit Code Review----开发商 Created-By: Apache Maven 3.5.0----编译打包工具 Build-Jdk: 1.8.0_101----编译的JDK版本 Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/cookbook-plugin ----代码实现URL 二、如何编译插件请参考:http://blog.csdn.net/czq7511/article/details/73189168 三、Gerrit插件的前端独立UI主要是用GWT来开发,通过在pom.xml里来指定编译入口,如(module是入口类,省去了扩展名gwt.xml): <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>gwt-maven-plugin</artifactId> <version>2.7.0</version> <configuration> <module>com.googlesource.gerrit.plugins.cookbook.HelloForm</module> <disableClassMetadata>true</disableClassMetadata> <disableCastChecking>true</disableCastChecking> <webappDirectory>${project.build.directory}/classes/static</webappDirectory> </configuration> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin> GWT编译时,会默认对与gwt.xml文件同目录下的client和public两目录下的所以文件进行编译打包,包的名字默认是插件名,可以修改,可以在gwt.xml里的rename-to属性上指定。该包名主要用于gerrit加载插件时,Module模块里指定加载,以便加载前端业务,具体加载代码: DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new GwtPlugin("rename-to属性上指定的名字")); 四、对插件创建的screen的引用,必须加 #/x/,因为screen被加载时,会在指定的token前加上/x/ 标签,可参见加载源码: gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/plugin.java 里的注释说明 五、使用MenuItem时,第二个参当我若有 # 号,第三个参数要填写,且为空字符,否则 # 号在前端会被显示为 %23 六、如何判断用户是否已登录: 1、菜单:继承于菜单TopMenu.class的类有一个构建方法,构建时会传入用户信息类,可通过该类来判断,如: @Inject public ImporterTopMenu(@PluginName String pluginName, Provider<CurrentUser> userProvider) { if (userProvider.get().isIdentifiedUser()) { //这里表示用户已登录 } else { //这里表示用户未登录 } } 2、服务:服务类是继承于RestModifyView.class的,该类的构建方法也会传入 用户信息类 参数,用法同 1