roll_dev.dart 5.79 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:args/args.dart';
6 7
import 'package:args/command_runner.dart';
import 'package:file/file.dart';
8
import 'package:meta/meta.dart';
9 10 11 12 13 14
import 'package:platform/platform.dart';

import './repository.dart';
import './stdio.dart';
import './version.dart';

15 16 17 18 19 20 21 22
const String kIncrement = 'increment';
const String kCommit = 'commit';
const String kRemoteName = 'remote';
const String kJustPrint = 'just-print';
const String kYes = 'yes';
const String kForce = 'force';
const String kSkipTagging = 'skip-tagging';

23
/// Create a new dev release without cherry picks.
24 25
class RollDevCommand extends Command<void> {
  RollDevCommand({
26 27 28 29
    required this.checkouts,
    required this.fileSystem,
    required this.platform,
    required this.stdio,
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
  }) {
    argParser.addOption(
      kIncrement,
      help: 'Specifies which part of the x.y.z version number to increment. Required.',
      valueHelp: 'level',
      allowed: <String>['y', 'z', 'm'],
      allowedHelp: <String, String>{
        'y': 'Indicates the first dev release after a beta release.',
        'z': 'Indicates a hotfix to a stable release.',
        'm': 'Indicates a standard dev release.',
      },
    );
    argParser.addOption(
      kCommit,
      help: 'Specifies which git commit to roll to the dev branch. Required.',
      valueHelp: 'hash',
      defaultsTo: null, // This option is required
    );
    argParser.addFlag(
      kForce,
      abbr: 'f',
      help: 'Force push. Necessary when the previous release had cherry-picks.',
      negatable: false,
    );
    argParser.addFlag(
      kJustPrint,
      negatable: false,
      help:
          "Don't actually roll the dev channel; "
          'just print the would-be version and quit.',
    );
    argParser.addFlag(
      kSkipTagging,
      negatable: false,
      help: 'Do not create tag and push to remote, only update release branch. '
      'For recovering when the script fails trying to git push to the release branch.'
    );
67 68 69 70 71 72 73 74 75 76 77
    argParser.addFlag(
      kYes,
      negatable: false,
      abbr: 'y',
      help: 'Skip the confirmation prompt.',
    );
    argParser.addOption(
      kRemoteName,
      help: 'Specifies which git remote to fetch from.',
      defaultsTo: 'upstream',
    );
78
  }
79

80
  final Checkouts checkouts;
81 82 83
  final FileSystem fileSystem;
  final Platform platform;
  final Stdio stdio;
84

85 86
  @override
  String get name => 'roll-dev';
87

88 89 90
  @override
  String get description =>
      'For publishing a dev release without cherry picks.';
91

92 93 94
  @override
  void run() {
    rollDev(
95
      argResults: argResults!,
96
      repository: FrameworkRepository(checkouts),
97 98
      stdio: stdio,
      usage: argParser.usage,
99 100 101 102 103 104 105
    );
  }
}

/// Main script execution.
///
/// Returns true if publishing was successful, else false.
106 107
@visibleForTesting
bool rollDev({
108 109 110 111
  required String usage,
  required ArgResults argResults,
  required Stdio stdio,
  required FrameworkRepository repository,
112
}) {
113
  final String remoteName = argResults[kRemoteName] as String;
114 115 116 117
  final String level = argResults[kIncrement] as String;
  final String commit = argResults[kCommit] as String;
  final bool justPrint = argResults[kJustPrint] as bool;
  final bool autoApprove = argResults[kYes] as bool;
118
  final bool force = argResults[kForce] as bool;
119
  final bool skipTagging = argResults[kSkipTagging] as bool;
120

121 122 123 124
  if (level == null || commit == null) {
    stdio.printStatus(
        'roll_dev.dart --increment=level --commit=hash • update the version tags '
        'and roll a new dev build.\n$usage');
125
    return false;
126 127
  }

128
  final String remoteUrl = repository.remoteUrl(remoteName);
129

130
  if (!repository.gitCheckoutClean()) {
131
    throw Exception(
132 133
        'Your git repository is not clean. Try running "git clean -fd". Warning, '
        'this will delete files! Run with -n to find out which ones.');
134 135
  }

136 137 138 139
  repository.fetch(remoteName);

  // Verify [commit] is valid
  repository.reverseParse(commit);
140

141 142 143
  stdio.printStatus('remoteName is $remoteName');
  final Version lastVersion =
      Version.fromString(repository.getFullTag(remoteName));
144

145 146 147
  final Version version =
      skipTagging ? lastVersion : Version.increment(lastVersion, level);
  final String tagName = version.toString();
148

149 150 151
  if (repository.reverseParse(lastVersion.toString()).contains(commit.trim())) {
    throw Exception(
        'Commit $commit is already on the dev branch as $lastVersion.');
152
  }
153

154
  if (justPrint) {
155
    stdio.printStatus(tagName);
156
    return false;
157 158
  }

159 160 161
  if (skipTagging && !repository.isCommitTagged(commit)) {
    throw Exception(
        'The $kSkipTagging flag is only supported for tagged commits.');
162
  }
163

164 165 166 167
  if (!force && !repository.isAncestor(commit, lastVersion.toString())) {
    throw Exception(
        'The previous dev tag $lastVersion is not a direct ancestor of $commit.\n'
        'The flag "$kForce" is required to force push a new release past a cherry-pick.');
168 169
  }

170
  final String hash = repository.reverseParse(commit);
171

172 173
  // [commit] can be a prefix for [hash].
  assert(hash.startsWith(commit));
174 175

  // PROMPT
176
  if (autoApprove) {
177 178
    stdio.printStatus(
        'Publishing Flutter $version ($hash) to the "dev" channel.');
179
  } else {
180 181 182 183 184
    stdio.printStatus('Your tree is ready to publish Flutter $version '
        '($hash) to the "dev" channel.');
    stdio.write('Are you? [yes/no] ');
    if (stdio.readLineSync() != 'yes') {
      stdio.printError('The dev roll has been aborted.');
185
      return false;
186
    }
187 188
  }

189
  if (!skipTagging) {
190
    repository.tag(commit, version.toString(), remoteName);
191
  }
192

193 194 195 196 197
  repository.updateChannel(
    commit,
    remoteName,
    'dev',
    force: force,
198 199
  );

200 201
  stdio.printStatus(
    'Flutter version $version has been rolled to the "dev" channel at $remoteUrl.',
202
  );
203
  return true;
204
}