Bump 版本并从一个分支发布包,但将标签保留在另一个分支中

Posted

技术标签:

【中文标题】Bump 版本并从一个分支发布包,但将标签保留在另一个分支中【英文标题】:Bump version and publish packages from one branch but keep tags in another branch 【发布时间】:2019-08-26 14:54:55 【问题描述】:

我正在使用Lerna 将包含许多相互依赖的包的项目迁移到monorepo。我们在开发过程中遵循Gitflow workflow 之类的内容。主要概念是在develop 分支和从develop 创建并合并回的所有其他分支(功能、错误修复等)中进行所有源代码更改。只要新版本的包准备好了,我们就会通过npm publishyarn publish 发布它,然后将它合并到master 分支并手动标记它,方法如下:

$ git checkout develop

对源代码进行一些更改,包括版本冲突...

$ git add -A
$ git commit -m "Make some changes and version bump."
$ git checkout master
$ git merge --no-ff develop -m "Version 0.14.1."
$ git tag -a 0.14.1 -m "Version 0.14.1."

现在我想用 Lerna 管理所有包来实现同样的目标。查看文档,我说publish 命令依赖于version 命令,而changed 命令在后台使用changed 命令来检测自最新版本以来包中所做的更改:

列出自上次标记发布以来已更改的本地包

考虑在一个包中的develop 分支中进行了一些更改(例如,@geoapps/layout

$ lerna changed

说所有的包都变了(这不是我所期望的):

info cli using local version of lerna
lerna notice cli v3.13.1
lerna info Assuming all packages changed
@geoapps/angle
@geoapps/camera-scene-mode-switcher
...
@geoapps/tracer
@geoapps/vector
lerna success found 39 packages ready to publish

我猜这是因为 Lerna 在 develop 分支中寻找标记的提交进行比较,但在那里没有找到任何东西。如果我将源代码更改提交到master 分支

然后 Lerna 在单个 @geoapps/layout 包中正确检测到它们:

$ git checkout master
$ lerna changed
info cli using local version of lerna
lerna notice cli v3.13.1
lerna info Looking for changed packages since 0.14.1
@geoapps/layout
lerna success found 1 package ready to publish

但是在master 分支中进行更改也不是我想做的。 include-merged-tags 是我尝试使用的另一个选项,但似乎它仅在标记提交也是 develop 分支历史的一部分时才有效:

$ git checkout develop
$ git merge --no-ff master -m "Sync with master."

$ lerna changed --include-merged-tags
info cli using local version of lerna
lerna notice cli v3.13.1
lerna info Looking for changed packages since 0.14.1
@geoapps/layout
lerna success found 1 package ready to publish

由于master 分支中标记的所有源代码更改都存在于develop 分支中,我想知道是否可以强制 Lerna 比较 develop 分支中所做的更改,而不是来自 master 的标记提交,而是与他们的父提交 (0.14.1^2) 也属于 develop。有可能吗?

环境:

$ node --version
v10.15.0
$ npm --version
6.9.0
$ yarn --version
1.15.2
$ lerna --version
3.13.1

【问题讨论】:

在GitHub 上发布了相同的问题/功能请求。 【参考方案1】:

Lerna 的核心开发人员says 认为 Lerna 不适合使用 Gitflow 工作流程。多说一句,prohibited 发布包来检测它们从特定提交(在另一个分支中标记的提交)的更改。最新的标记版本应属于进行更改的同一分支。

牢记它以及我们希望继续使用 Gitflow 的愿望,我决定修补 Lerna 以实现所需的行为。只需created git patch 并使用 Lerna 将其放在我的项目的根目录中。

lerna-version-since.patch

diff --git a/commands/version/command.js b/commands/version/command.js
index da9b1c00..3c5e19e2 100644
--- a/commands/version/command.js
+++ b/commands/version/command.js
@@ -104,6 +104,11 @@ exports.builder = (yargs, composed) => 
       requiresArg: true,
       defaultDescription: "alpha",
     ,
+    since: 
+      describe: "Look for changes since specified commit instead of last tagged release",
+      type: "string",
+      requiresArg: true,
+    ,
     "sign-git-commit": 
       describe: "Pass the `--gpg-sign` flag to `git commit`.",
       type: "boolean",

如果commands/version/command.js 发生变化,我们可能会更新补丁。为了应用补丁,应该运行以下命令:

$ git apply -p3 --directory node_modules/@lerna/version lerna-version-since.patch

修补 Lerna 后,现在可以在 develop 分支中碰撞和发布,并在 master 中标记发布。为了让事情变得更简单,我编写了一个名为lerna-gitflow.js 的脚本,它可以自动生成所有内容。这是package.json的脚本部分:

"scripts": 
  "publish:major": "./lerna-gitflow.js publish major",
  "publish:minor": "./lerna-gitflow.js publish minor",
  "publish:patch": "./lerna-gitflow.js publish patch",
  "changes": "./lerna-gitflow.js changes",
  "postinstall": "./lerna-gitflow.js patch"

所有这些publish:*changes 命令都应该从开发分支运行(默认为develop)。

changes 命令仅显示自发布分支中的最新发布标记(默认为master)以来开发分支(develop)中更改的包。

publish 命令做了两件事:

更新包的package.json文件中的版本,根目录package.jsonlerna.json,并在本地提交到develop分支(可以单独运行,例如./lerna-gitflow.js version patch); 将更改的包从develop 分支发布到npm 注册表,然后将更改合并到master 分支而不使用快进并在那里标记新版本(也可以通过运行./lerna-gitflow.js publish --skip-version 单独完成)。

postinstall 脚本尝试在任何 npm installyarn install 调用上修补 Lerna,否则使一切正常工作所需的更改将丢失。

lerna-gitflow.js

#!/usr/bin/env node
const path = require('path');
const yargs = require('yargs');
const execa = require('execa');
const jsonfile = require('jsonfile');

const noop = () => ;

async function lernaCommand(command, options) 
  const  devBranch  = options;
  const branch = await getCurrentBranch();
  if (branch !== devBranch) 
    return Promise.reject(
      `You should be in "$devBranch" branch to detect changes but current branch is "$branch".`
    );
  
  const latestVersion = await getLatestVersion();

  const bumpVersion = async bump => 
    await lernaVersion(latestVersion, bump);
    const version = await getLernaVersion();
    const packageJsonPath = path.resolve(__dirname, 'package.json');
    const packageJson = await jsonfile.readFile(packageJsonPath);
    packageJson.version = version;
    await jsonfile.writeFile(packageJsonPath, packageJson,  spaces: 2 );
    await exec('git', ['add', '-A']);
    await exec('git', ['commit', '-m', 'Version bump.']);
    return version;
  ;

  const reject = e => 
    if (typeof e === 'string') 
      return Promise.reject(e);
    
    return Promise.reject('Unable to detect any changes in packages, probably nothing has changed.');
  ;

  switch (command) 
    case 'publish': 
      const  bump, skipVersion, releaseBranch  = options;
      if (releaseBranch === devBranch) 
        return Promise.reject('Release and development branches can\'t be the same.');
      
      try 
        const version = skipVersion ? await getLernaVersion() : await bumpVersion(bump);
        await lernaPublish(latestVersion, version);
        await exec('git', ['checkout', releaseBranch]);
        await exec('git', ['merge', '--no-ff', devBranch, '-m', `Version $version.`]);
        await exec('git', ['tag', '-a', version, '-m', `Version $version.`]);
        await exec('git', ['checkout', devBranch]);
      
      catch (e) 
        return reject(e);
      
      break;
    

    case 'version': 
      const  bump  = options;
      try 
        await bumpVersion(bump);
      
      catch (e) 
        return reject(e);
      
      break;
    

    case 'changed': 
      try 
        await lernaChanged(latestVersion);
      
      catch (e) 
        return reject(e);
      
      break;
    
  


async function lernaPublish(since, version) 
  if (since === version) 
    return Promise.reject(`Unable to publish packages with same version $version.`);
  
  return exec('lerna', ['publish', '--since', since, version, '--no-push', '--no-git-tag-version', '--yes']);


async function lernaVersion(since, bump) 
  return exec('lerna', ['version', '--since', since, bump, '--no-push', '--no-git-tag-version', '--yes']);


async function lernaChanged(since) 
  return exec('lerna', ['changed', '--since', since]);


async function patch() 
  try 
    await exec('git', ['apply', '-p3', '--directory', 'node_modules/@lerna/version', 'lerna-version-since.patch']);
  
  catch (e) 
    return Promise.reject('Lerna Gitflow patch is not applied (probably, it\'s already applied before).');
  


async function getCurrentBranch() 
  const  stdout  = await exec('git', ['branch']);
  const match = stdout.match(/\* ([\S]+)/);
  if (match === null) 
    return Promise.reject('Unable to detect current git branch.');
  
  return match[1];


async function getLatestTaggedCommit() 
  const  stdout  = await exec('git', ['rev-list', '--tags', '--max-count', 1]);
  if (!stdout) 
    return Promise.reject('Unable to find any tagged commit.');
  
  return stdout;


async function getLatestVersion() 
  const commit = await getLatestTaggedCommit();
  const  stdout  = await exec('git', ['describe', '--tags', commit]);
  return stdout;


async function getLernaVersion() 
  const lernaJson = await jsonfile.readFile(path.resolve(__dirname, 'lerna.json'));
  return lernaJson.version;


function exec(cmd, args, opts) 
  console.log(`$ $cmd $args.join(' ')`);
  const promise = execa(cmd, args, opts);
  promise.stdout.pipe(process.stdout);
  promise.stderr.pipe(process.stderr);
  return promise;


yargs
  .wrap(null)
  .strict(true)
  .help(true, 'Show help')
  .version(false)
  .fail((msg, error) => 
    console.error(error);
    if (msg) 
      console.error(msg);
    
  )
  .demandCommand()
  .command(
    'publish <bump>',
    'Bump and commit packages\' in development branch, then publish, merge into and tag in release branch',
    yargs => yargs
      .positional('bump', 
        describe: 'Type of version update',
        type: 'string'
      )
      .option('skip-version', 
        describe: 'Skip version bumping and commiting in development branch',
        type: 'boolean',
        default: false
      ),
    opts => lernaCommand('publish', opts)
  )
  .command(
    'version <bump>',
    'Bump and commit packages\' version in development branch',
    yargs => yargs
      .positional('bump', 
        describe: 'Type of version update',
        type: 'string'
      ),
    opts => lernaCommand('version', opts)
  )
  .command(
    'changes',
    'Detect packages changes since latest release',
    noop,
    opts => lernaCommand('changed', opts)
  )
  .command('patch', 'Patch Lerna to use with Gitflow', noop, () => patch())
  .options(
    'dev-branch': 
      describe: 'Name of git development branch',
      type: 'string',
      demandOption: true,
      default: 'develop'
    ,
    'release-branch': 
      describe: 'Name of git release branch',
      type: 'string',
      demandOption: true,
      default: 'master'
    
  )
  .parse();

【讨论】:

以上是关于Bump 版本并从一个分支发布包,但将标签保留在另一个分支中的主要内容,如果未能解决你的问题,请参考以下文章

如何替换html标签但将文本保留在两者之间?

SVN 从存储库中删除更改,但将它们保留在我的结帐中?

Bump 版本代表啥?

GitLab重命名分支并从另一个重新开始

多版本OSS开发有一个好的模式吗?

PopBackStack 但将第一个片段保留在 android