likes
comments
collection
share

Git用户管理进阶:Deno CLI的应用实践

作者站长头像
站长
· 阅读数 52

为什么要切换Git用户呢?

通常来说,我们在GitLab上使用工作邮箱,而GitHub、Gitee等网站使用我们的个人邮箱。我们的电脑上,Git账号一般都是全局设置个人邮箱,在公司的项目里,再设置工作邮箱。

而有时候,有的电脑上是配置的这个,有的配置是那个,在提交记录里就有些混乱了,个人项目还好,团队协作中就显得很不专业了。

Git用户管理进阶:Deno CLI的应用实践

怎么切换Git用户呢?

切换Git用户的几种方法

方法1:Git命令

我们知道,全局设置用户是这样的:

git config --global user.name  XXX
git config --global user.email XXX@xxx.com

那么某个工程设置用户只需要去掉--global即可:

git config user.name  XXX
git config user.email XXX@xxx.com

但每次都执行这两句命令也挺麻烦的,有没有更便捷的方法呢?

方法2:sh脚本

最简单是建个sh的脚本文件,每次都执行一次。

#!/bin/sh

git config user.name "XXX"
git config user.email "XXX@xxx.com"

缺点是每次都得把这个文件复制一遍,那样跟把两个命令合并成一行有什么区别?

方法3:Deno脚本

另一种就是用Deno写个脚本,在子任务里执行这两句:

export async function runTask(str: string): Promise<string> {
  const [cmd, ...args] = str.split(" ");
  const command = new Deno.Command(cmd, {
    args,
  });
  const { code, stdout, stderr } = await command.output();
  const te = new TextDecoder();
  if (code !== 0) {
    throw new Error(te.decode(stderr));
  }
  return te.decode(stdout);
}

export async function runTasks(arr: string[]) {
  for (const str of arr) {
    await runTask(str);
    console.log(`运行任务success:${str}`);
  }
}

if (import.meta.main) {
  await runTasks([
    `git config user.name "XXX"`,
    `git config user.email "XXX@xxx.com"`,
  ]);
}

我们使用deno install把它安装为全局的命令,然后就可以到处使用了。

缺点是这个文件在本地还好,如果把它发布到CDN上,里面再包含我们的用户名,有种暴露隐私的感觉。如果Git全局设置的是工作邮箱,用它来切换个人邮箱可能比较合适。

方法4:Git includeif

Git配置文件支持includeincludeif关键字来包含其它配置文件,对我们这种需求,可以使用includeif

首先,打开当前用户的根目录下,找到.gitconfig文件:

$ cat ~/.gitconfig

# 下面是我的文件内容,之前git config --global user.name修改的就是这里
[user]
	name = jw397
	email = jw397@126.com

[init]
	defaultBranch = main

在下面加入以下代码:

[includeIf "gitdir:~/wk/gitlab/"]
    	path = .gitconfig-gitlab

在当前目录下,新建一个.gitconfig-gitlab文件:

$ vim .gitconfig-gitlab

[user]
    name = 测试
    email = test@test.com

这样之后在~/wk/gitlab/这个目录下,所有的项目默认就是新配置的用户名和邮箱了,除非你单独使用git config user.name设置过,在当前目录下使用这个命令,本质上修改的是.git/config文件。

手写一个CLI进行切换

本来上面的方法4已经很完美了,为什么还要折腾一个新的CLI呢?

因为我是按代码类型分的文件夹,这样的话需要重新整理一遍(虽然也不是不可以): Git用户管理进阶:Deno CLI的应用实践

上面Deno脚本的方案,用户名、邮箱如果修改为可以动态配置的,就比较合理了。对于这个简单的功能,我们可以选择将数据存储到JSON文件里,也可以存储到LocalStorage里,当然,也可以使用目前Deno正在主推的本地数据库Deno.kv

刚好我想试用一个Deno.kv,所以决定模仿nrm实现一个简单的切换Git脚本管理器,就叫gum(Git User Manager)吧。

命令无非是增(add)、删(del)、改(改就算了,直接用add替换)、查(list)、切换(use)。

数据存储

存储3个字段,使用alias作为key值:

interface GitUser {
  username: string;
  email: string;
  alias: string; // 用作key值
}

先打开Deno.kv:

import os from "node:os";
import { join } from "https://deno.land/std@0.202.0/path/mod.ts";
import { ensureFile } from "https://deno.land/std@0.202.0/fs/ensure_file.ts";

const PREFIX = "git_user";
const kvPath = join(os.homedir(), ".deno", "kv", PREFIX);
await ensureFile(kvPath); // 如果不存在则创建
const kv = await Deno.openKv(kvPath); // 指定路径后,版本升级数据也不会丢失

之所以这么复杂,是因为需要指定kv文件的存储路径,如果不指定的话,默认位置在Deno的缓存目录的location_data/${hash}/kv.sqlite3,后续版本升级hash值变化,数据就丢失了。

再封装几个函数:

async function getUserList(): Promise<GitUser[]> {
  const entries = kv.list<GitUser>({ prefix: [PREFIX] });
  const users: GitUser[] = [];
  for await (const entry of entries) {
    users.push(entry.value);
  }
  return users;
}

async function addUser(user: GitUser) {
  await kv.set([PREFIX, user.alias], user);
}

async function getUser(alias: string): Promise<GitUser | null> {
  const entry = await kv.get<GitUser>([PREFIX, alias]);
  if (entry) {
    return entry.value;
  }
  return null;
}

async function removeUser(alias: string) {
  await kv.delete([PREFIX, alias]);
}

add

我们先实现add命令,把它制作为可交互的,也就是让用户自己输入用户名、邮箱和alias。

为了方便,我们选用cliffy来进行交互。

import { Input } from "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/mod.ts";
import { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts";

const add = new Command()
  .description("Add one git user.")
  .action(async () => {
    const name: string = await Input.prompt({
      message: `Set a username`,
    });
    const email: string = await Input.prompt({
      message: `Set a email`,
    });

    const alias: string = await Input.prompt({
      message: `Set an alias`,
      default: name,
    });
    console.log(`${name} ${email} ${alias}`);
  });

await new Command()
  .name("gum")
  .description("Change the git user in current project")
  .command("add", add)
  .parse(Deno.args);

执行命令:

$ deno run mod.ts add
? Set a username › aa
? Set a email › bb
? Set an alias (aa) › cc
aa bb cc

上面已经写好了添加的接口,所以直接调用即可:

const user: GitUser = {
  username: name,
  email,
  alias,
};
await addUser(user);

list

增加之后,我们需要显示所有用户信息与当前用户。

先调用Git命令获取当前用户名称,再匹配出来记录的用户信息:

async function getCurrentUser(): Promise<GitUser | undefined> {
  const username  = await runTask("git config user.name", false);
  const allUsers = await getUserList();
  return allUsers.find((user) => user.username === username.trim());
}

将用户信息绘制成一个表格

import {
  Row,
  Table,
} from "https://deno.land/x/cliffy@v1.0.0-rc.3/table/mod.ts";

async function showGitList() {
  const users = await getUserList();
  const currentUser = await getCurrentUser();
  const rows = users.map((user) => {
    const isCurrent = currentUser?.alias === user.alias;
    return new Row(
      user.alias,
      user.username,
      user.email,
      isCurrent ? "✓" : "",
    ).border();
  });
  new Table()
    .header(Row.from(["Alias", "UserName", "Email", "Current"]).border())
    .body(rows)
    .render();
}

这样list命令就有了:

const list = new Command()
  .description("List all git users.")
  .action(showGitList);

await new Command()
  .name("gum")
  .default("list")
  .command("list ls", list)
  .parse(Deno.args);

效果是这样的:

$ gum 
# 等同于
$ gum list

┌───────┬──────────┬───────────────┬─────────┐
│ Alias │ UserName │ Email         │ Current │
├───────┼──────────┼───────────────┼─────────┤
│ jw    │ jw397    │ jw397@126.com │ ✓       │
├───────┼──────────┼───────────────┼─────────┤
testtesttest@test.cn  │         │
└───────┴──────────┴───────────────┴─────────┘

use

下来就该切换用户信息了,直接调用这俩Git命令即可:

async function changeGitUser(username: string, email: string) {
  await runTasks([
    `git config user.name ${username}`,
    `git config user.email ${email}`,
  ]);
}

添加use命令:

const use = new Command()
    .description("Change current git user.")
    .arguments("<alias:string>")
    .action(async (_options: unknown, alias: string) => {
      const user = await getUser(alias);
      if (!user) {
        console.log(error(`Not find user by alias: ${alias}`));
        return;
      }
      console.log(`${user.alias} ------ ${user.username} ---- ${user.email}`);
      await changeGitUser(user.username, user.email);
      console.log(info("Git user changed"));
      await showGitList();
    });
await new Command()
    .name("gum")
    .default("list")
    .command("list ls", list)
    .command("add", add)
    .command("use", use)
    .parse(Deno.args);

del

删除时需要注意下如果是当前用户,不允许删除。

const del = new Command()
  .description("Remove one git user.")
  .arguments("<alias:string>")
  .action(async (_options: unknown, alias: string) => {
    const user = await getUser(alias);
    if (!user) {
      console.log(error(`Not find user by alias: ${alias}`));
      return;
    }
    const currentUser = await getCurrentUser();
    if (currentUser?.alias === alias) {
      console.log(error(`Can't remove current git user`));
      return;
    }
    console.log(`${user.alias} ------ ${user.username} ---- ${user.email}`);
    await removeUser(alias);
    console.log(info("Git user has been removed"));
    await showGitList();
  });

await new Command()
    .name("gum")
    .default("list")
    .command("list ls", list)
    .command("add", add)
    .command("delete del remove", del)
    .command("use", use)
    .parse(Deno.args);

upgrade

本来上面就算结束了,但看cliffy还提供了一个更新的处理,就把它加进来了:

import {
  DenoLandProvider,
  UpgradeCommand,
} from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/mod.ts";
import pkg from "../../deno.json" with { type: "json" };

const upgrade = new UpgradeCommand({
  main: "cli/git/user_change.ts",
  args: [
    "--allow-net",
    "--allow-run",
    "--allow-env",
    "--allow-write",
    "--allow-read",
    "--unstable",
  ],
  provider: new DenoLandProvider({ name: "jw_cli" }),
});

await new Command()
    .name("gum")
    .version("v" + pkg.version)
    .command("upgrade", upgrade)
    .parse(Deno.args);

这里有几处需要注意:

  1. version是发布到deno.land的版本号,我的发布都带了v,记录在deno.json里,所以需要读取这个文件
  2. upgrade的main参数是这个脚本的相对路径,args是脚本需要的权限,DenoLandProvider的参数是发布到deno.land中的包名。底层是调用的这个接口http://cdn.deno.land/jw_cli/meta/versions.json,会得到所有的包的版本信息:
{"latest":"v0.9.2","versions":["v0.9.2","v0.9.1","v0.9.0","v0.8.0","v0.7.0","v0.6.0","v0.5.2","v0.5.1","v0.5.0","v0.4.0","v0.3.1","v0.3.0","v0.2.7","v0.2.6","v0.2.5","v0.2.4","v0.2.3","v0.2.2","v0.2.1","v0.2.1","v0.2.0","v0.1.6","v0.1.5","v0.1.4","v0.1.3","v0.1.2","v0.1.1","v0.1.0","v0.0.10","v0.0.9","v0.0.8","v0.0.7","v0.0.6","v0.0.5","v0.0.4","v0.0.3","v0.0.2","0.0.2","v0.0.1"]}

这样gum upgrade -l就会展示所有版本: Git用户管理进阶:Deno CLI的应用实践

可以使用gum upgrade --version v0.9.2来降级到指定版本。

gum --help每次都会请求这个版本,如果有新版本,会提示你更新:

Usage:   gum   
Version: v0.9.2  (New version available: v0.9.3. Run 'gum upgrade' to upgrade to the latest version!)

deno: 1.37.0     
v8: 11.8.172.3   
typescript: 5.2.2

Description:

  Change the git user in current project

Options:

  -h, --help     - Show this help.                            
  -V, --version  - Show the version number for this program.  

Commands:

  list, ls                      - List all git users.                               
  add                           - Add one git user.                                 
  delete, del, remove  <alias>  - Remove one git user.                              
  use                  <alias>  - Change current git user.                          
  upgrade                       - Upgrade gum executable to latest or given version.

不过,如果新的版本有权限申请,这样升级后不会成功,每次命令行都会提示让你添加允许:

$ gum
┌ ⚠️  Deno requests env access to "HOME".
├ Run again with --allow-env to bypass this prompt.
└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions) 

这时就需要手动重新安装下了。

安装

我们的CLI就算完成了。 我本地开发时,使用命令安装:

deno install --allow-run --allow-net --allow-read --allow-write --allow-env --unstable -n gum  -f ./cli/git/user_change.ts

而现在可以使用deno.land上的文件:

deno install --allow-run --allow-net --allow-read --allow-write --allow-env --unstable -n gum  -f https://deno.land/x/jw_cli@v0.9.3/cli/git/user_change.ts

有兴趣的同学可以试试。

总结

本文介绍了几种切换Git用户的方法,核心说起来只有2种:

  1. 在工程下调用git config user.xxx修改当前工程的配置项(.git/config文件)。
  2. 使用includeif,读取全局配置时,某路径下读取另一个配置文件。

我们使用Deno封装了一个CLI,可以便捷地在某工程下切换Git用户,更多目的是为了熟悉Deno.kv与CLI交互。

Deno开发CLI的优势还是很大的,不像Node.js一样需要做额外的配置,TypeScript也是开箱即用,又有完善的权限管理,对于第三方的代码也能直接查看源码不用担心投毒,推荐大家平时寻找合适的场景玩一玩。

转载自:https://juejin.cn/post/7283532551472332857
评论
请登录