Skip to content

Commit b0d4601

Browse files
authored
[flutter_migrate] Custom merge (#2733)
1 parent 0e6f7fd commit b0d4601

File tree

2 files changed

+398
-0
lines changed

2 files changed

+398
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'base/file_system.dart';
6+
import 'base/logger.dart';
7+
import 'base/project.dart';
8+
import 'flutter_project_metadata.dart';
9+
import 'utils.dart';
10+
11+
/// Handles the custom/manual merging of one file at `localPath`.
12+
///
13+
/// The `merge` method should be overridden to implement custom merging.
14+
abstract class CustomMerge {
15+
CustomMerge({
16+
required this.logger,
17+
required this.localPath,
18+
});
19+
20+
/// The local path (with the project root as the root directory) of the file to merge.
21+
final String localPath;
22+
final Logger logger;
23+
24+
/// Called to perform a custom three way merge between the current,
25+
/// base, and target files.
26+
MergeResult merge(File current, File base, File target);
27+
}
28+
29+
/// Manually merges a flutter .metadata file.
30+
///
31+
/// See `FlutterProjectMetadata`.
32+
class MetadataCustomMerge extends CustomMerge {
33+
MetadataCustomMerge({
34+
required super.logger,
35+
}) : super(localPath: '.metadata');
36+
37+
@override
38+
MergeResult merge(File current, File base, File target) {
39+
final FlutterProjectMetadata result = computeMerge(
40+
FlutterProjectMetadata(current, logger),
41+
FlutterProjectMetadata(base, logger),
42+
FlutterProjectMetadata(target, logger),
43+
logger,
44+
);
45+
return StringMergeResult.explicit(
46+
mergedString: result.toString(),
47+
hasConflict: false,
48+
exitCode: 0,
49+
localPath: localPath,
50+
);
51+
}
52+
53+
FlutterProjectMetadata computeMerge(
54+
FlutterProjectMetadata current,
55+
FlutterProjectMetadata base,
56+
FlutterProjectMetadata target,
57+
Logger logger) {
58+
// Prefer to update the version revision and channel to latest version.
59+
final String? versionRevision = target.versionRevision ??
60+
current.versionRevision ??
61+
base.versionRevision;
62+
final String? versionChannel =
63+
target.versionChannel ?? current.versionChannel ?? base.versionChannel;
64+
// Prefer to leave the project type untouched as it is non-trivial to change project type.
65+
final FlutterProjectType? projectType =
66+
current.projectType ?? base.projectType ?? target.projectType;
67+
final MigrateConfig migrateConfig = mergeMigrateConfig(
68+
current.migrateConfig,
69+
target.migrateConfig,
70+
);
71+
final FlutterProjectMetadata output = FlutterProjectMetadata.explicit(
72+
file: current.file,
73+
versionRevision: versionRevision,
74+
versionChannel: versionChannel,
75+
projectType: projectType,
76+
migrateConfig: migrateConfig,
77+
logger: logger,
78+
);
79+
return output;
80+
}
81+
82+
MigrateConfig mergeMigrateConfig(
83+
MigrateConfig current, MigrateConfig target) {
84+
// Create the superset of current and target platforms with baseRevision updated to be that of target.
85+
final Map<SupportedPlatform, MigratePlatformConfig> platformConfigs =
86+
<SupportedPlatform, MigratePlatformConfig>{};
87+
for (final MapEntry<SupportedPlatform, MigratePlatformConfig> entry
88+
in current.platformConfigs.entries) {
89+
if (target.platformConfigs.containsKey(entry.key)) {
90+
platformConfigs[entry.key] = MigratePlatformConfig(
91+
platform: entry.value.platform,
92+
createRevision: entry.value.createRevision,
93+
baseRevision: target.platformConfigs[entry.key]?.baseRevision);
94+
} else {
95+
platformConfigs[entry.key] = entry.value;
96+
}
97+
}
98+
for (final MapEntry<SupportedPlatform, MigratePlatformConfig> entry
99+
in target.platformConfigs.entries) {
100+
if (!platformConfigs.containsKey(entry.key)) {
101+
platformConfigs[entry.key] = entry.value;
102+
}
103+
}
104+
105+
// Ignore the base file list.
106+
final List<String> unmanagedFiles =
107+
List<String>.from(current.unmanagedFiles);
108+
for (final String path in target.unmanagedFiles) {
109+
if (!unmanagedFiles.contains(path) &&
110+
!MigrateConfig.kDefaultUnmanagedFiles.contains(path)) {
111+
unmanagedFiles.add(path);
112+
}
113+
}
114+
return MigrateConfig(
115+
platformConfigs: platformConfigs,
116+
unmanagedFiles: unmanagedFiles,
117+
);
118+
}
119+
}
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:file/memory.dart';
6+
7+
import 'package:flutter_migrate/src/base/file_system.dart';
8+
import 'package:flutter_migrate/src/base/logger.dart';
9+
import 'package:flutter_migrate/src/custom_merge.dart';
10+
import 'package:flutter_migrate/src/utils.dart';
11+
12+
import 'src/common.dart';
13+
14+
void main() {
15+
late FileSystem fileSystem;
16+
late BufferLogger logger;
17+
18+
setUpAll(() {
19+
fileSystem = MemoryFileSystem.test();
20+
logger = BufferLogger.test();
21+
});
22+
23+
group('.metadata merge', () {
24+
late MetadataCustomMerge merger;
25+
26+
setUp(() {
27+
merger = MetadataCustomMerge(logger: logger);
28+
});
29+
30+
testWithoutContext('merges empty', () async {
31+
const String current = '';
32+
const String base = '';
33+
const String target = '';
34+
final File currentFile = fileSystem.file('.metadata_current');
35+
final File baseFile = fileSystem.file('.metadata_base');
36+
final File targetFile = fileSystem.file('.metadata_target');
37+
38+
currentFile
39+
..createSync(recursive: true)
40+
..writeAsStringSync(current, flush: true);
41+
baseFile
42+
..createSync(recursive: true)
43+
..writeAsStringSync(base, flush: true);
44+
targetFile
45+
..createSync(recursive: true)
46+
..writeAsStringSync(target, flush: true);
47+
48+
final StringMergeResult result =
49+
merger.merge(currentFile, baseFile, targetFile) as StringMergeResult;
50+
expect(
51+
result.mergedString,
52+
'''
53+
# This file tracks properties of this Flutter project.
54+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
55+
#
56+
# This file should be version controlled.
57+
58+
version:
59+
revision: null
60+
channel: null
61+
62+
project_type: '''
63+
'''
64+
65+
66+
# Tracks metadata for the flutter migrate command
67+
migration:
68+
platforms:
69+
70+
# User provided section
71+
72+
# List of Local paths (relative to this file) that should be
73+
# ignored by the migrate tool.
74+
#
75+
# Files that are not part of the templates will be ignored by default.
76+
unmanaged_files:
77+
- 'lib/main.dart'
78+
- 'ios/Runner.xcodeproj/project.pbxproj'
79+
''');
80+
});
81+
82+
testWithoutContext('merge adds migration section', () async {
83+
const String current = '''
84+
# my own comment
85+
version:
86+
revision: abcdefg12345
87+
channel: stable
88+
project_type: app
89+
''';
90+
const String base = '''
91+
version:
92+
revision: abcdefg12345base
93+
channel: stable
94+
project_type: app
95+
migration:
96+
platforms:
97+
- platform: root
98+
create_revision: somecreaterevision
99+
base_revision: somebaserevision
100+
- platform: android
101+
create_revision: somecreaterevision
102+
base_revision: somebaserevision
103+
unmanaged_files:
104+
- 'lib/main.dart'
105+
- 'ios/Runner.xcodeproj/project.pbxproj'
106+
''';
107+
const String target = '''
108+
version:
109+
revision: abcdefg12345target
110+
channel: stable
111+
project_type: app
112+
migration:
113+
platforms:
114+
- platform: root
115+
create_revision: somecreaterevision
116+
base_revision: somebaserevision
117+
- platform: android
118+
create_revision: somecreaterevision
119+
base_revision: somebaserevision
120+
unmanaged_files:
121+
- 'lib/main.dart'
122+
- 'ios/Runner.xcodeproj/project.pbxproj'
123+
''';
124+
final File currentFile = fileSystem.file('.metadata_current');
125+
final File baseFile = fileSystem.file('.metadata_base');
126+
final File targetFile = fileSystem.file('.metadata_target');
127+
128+
currentFile
129+
..createSync(recursive: true)
130+
..writeAsStringSync(current, flush: true);
131+
baseFile
132+
..createSync(recursive: true)
133+
..writeAsStringSync(base, flush: true);
134+
targetFile
135+
..createSync(recursive: true)
136+
..writeAsStringSync(target, flush: true);
137+
138+
final StringMergeResult result =
139+
merger.merge(currentFile, baseFile, targetFile) as StringMergeResult;
140+
expect(result.mergedString, '''
141+
# This file tracks properties of this Flutter project.
142+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
143+
#
144+
# This file should be version controlled.
145+
146+
version:
147+
revision: abcdefg12345target
148+
channel: stable
149+
150+
project_type: app
151+
152+
# Tracks metadata for the flutter migrate command
153+
migration:
154+
platforms:
155+
- platform: root
156+
create_revision: somecreaterevision
157+
base_revision: somebaserevision
158+
- platform: android
159+
create_revision: somecreaterevision
160+
base_revision: somebaserevision
161+
162+
# User provided section
163+
164+
# List of Local paths (relative to this file) that should be
165+
# ignored by the migrate tool.
166+
#
167+
# Files that are not part of the templates will be ignored by default.
168+
unmanaged_files:
169+
- 'lib/main.dart'
170+
- 'ios/Runner.xcodeproj/project.pbxproj'
171+
''');
172+
});
173+
174+
testWithoutContext('merge handles standard migration flow', () async {
175+
const String current = '''
176+
# my own comment
177+
version:
178+
revision: abcdefg12345current
179+
channel: stable
180+
project_type: app
181+
migration:
182+
platforms:
183+
- platform: root
184+
create_revision: somecreaterevisioncurrent
185+
base_revision: somebaserevisioncurrent
186+
- platform: android
187+
create_revision: somecreaterevisioncurrent
188+
base_revision: somebaserevisioncurrent
189+
unmanaged_files:
190+
- 'lib/main.dart'
191+
- 'new/file.dart'
192+
''';
193+
const String base = '''
194+
version:
195+
revision: abcdefg12345base
196+
channel: stable
197+
project_type: app
198+
migration:
199+
platforms:
200+
- platform: root
201+
create_revision: somecreaterevisionbase
202+
base_revision: somebaserevisionbase
203+
- platform: android
204+
create_revision: somecreaterevisionbase
205+
base_revision: somebaserevisionbase
206+
unmanaged_files:
207+
- 'lib/main.dart'
208+
- 'ios/Runner.xcodeproj/project.pbxproj'
209+
''';
210+
const String target = '''
211+
version:
212+
revision: abcdefg12345target
213+
channel: stable
214+
project_type: app
215+
migration:
216+
platforms:
217+
- platform: root
218+
create_revision: somecreaterevisiontarget
219+
base_revision: somebaserevisiontarget
220+
- platform: android
221+
create_revision: somecreaterevisiontarget
222+
base_revision: somebaserevisiontarget
223+
unmanaged_files:
224+
- 'lib/main.dart'
225+
- 'ios/Runner.xcodeproj/project.pbxproj'
226+
- 'extra/file'
227+
''';
228+
final File currentFile = fileSystem.file('.metadata_current');
229+
final File baseFile = fileSystem.file('.metadata_base');
230+
final File targetFile = fileSystem.file('.metadata_target');
231+
232+
currentFile
233+
..createSync(recursive: true)
234+
..writeAsStringSync(current, flush: true);
235+
baseFile
236+
..createSync(recursive: true)
237+
..writeAsStringSync(base, flush: true);
238+
targetFile
239+
..createSync(recursive: true)
240+
..writeAsStringSync(target, flush: true);
241+
242+
final StringMergeResult result =
243+
merger.merge(currentFile, baseFile, targetFile) as StringMergeResult;
244+
expect(result.mergedString, '''
245+
# This file tracks properties of this Flutter project.
246+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
247+
#
248+
# This file should be version controlled.
249+
250+
version:
251+
revision: abcdefg12345target
252+
channel: stable
253+
254+
project_type: app
255+
256+
# Tracks metadata for the flutter migrate command
257+
migration:
258+
platforms:
259+
- platform: root
260+
create_revision: somecreaterevisioncurrent
261+
base_revision: somebaserevisiontarget
262+
- platform: android
263+
create_revision: somecreaterevisioncurrent
264+
base_revision: somebaserevisiontarget
265+
266+
# User provided section
267+
268+
# List of Local paths (relative to this file) that should be
269+
# ignored by the migrate tool.
270+
#
271+
# Files that are not part of the templates will be ignored by default.
272+
unmanaged_files:
273+
- 'lib/main.dart'
274+
- 'new/file.dart'
275+
- 'extra/file'
276+
''');
277+
});
278+
});
279+
}

0 commit comments

Comments
 (0)