braid 1.0.22 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +0 -2
- data/README.md +48 -0
- data/_config.yml +1 -0
- data/bin/braid +44 -7
- data/braid.gemspec +4 -0
- data/config_versions.md +58 -0
- data/lib/braid.rb +7 -0
- data/lib/braid/command.rb +7 -7
- data/lib/braid/commands/add.rb +3 -3
- data/lib/braid/commands/diff.rb +7 -14
- data/lib/braid/commands/push.rb +10 -4
- data/lib/braid/commands/setup.rb +5 -1
- data/lib/braid/commands/status.rb +5 -1
- data/lib/braid/commands/update.rb +6 -15
- data/lib/braid/commands/upgrade_config.rb +56 -0
- data/lib/braid/config.rb +166 -27
- data/lib/braid/mirror.rb +111 -11
- data/lib/braid/operations.rb +51 -35
- data/lib/braid/version.rb +1 -1
- data/spec/config_spec.rb +2 -2
- data/spec/fixtures/shiny-conf-1.0.9-lock/.braids.json +10 -0
- data/spec/fixtures/shiny-conf-1.0.9-lock/expected.braids.json +9 -0
- data/spec/fixtures/shiny-conf-1.0.9-lock/skit1/layouts/layout.liquid +219 -0
- data/spec/fixtures/shiny-conf-1.0.9-lock/skit1/preview.png +0 -0
- data/spec/fixtures/shiny-conf-breaking-changes/.braids +14 -0
- data/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/README.md +9 -0
- data/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/index.html +20 -0
- data/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/styles.css +17 -0
- data/spec/fixtures/shiny-conf-breaking-changes/expected.braids.json +10 -0
- data/spec/fixtures/shiny-conf-breaking-changes/skit1/layouts/layout.liquid +219 -0
- data/spec/fixtures/shiny-conf-breaking-changes/skit1/preview.png +0 -0
- data/spec/fixtures/shiny-conf-future/.braids.json +10 -0
- data/spec/fixtures/shiny-conf-future/skit1/layouts/layout.liquid +219 -0
- data/spec/fixtures/shiny-conf-future/skit1/preview.png +0 -0
- data/spec/fixtures/shiny-conf-json-old-name/.braids +9 -0
- data/spec/fixtures/shiny-conf-json-old-name/expected.braids.json +10 -0
- data/spec/fixtures/shiny-conf-json-old-name/skit1/layouts/layout.liquid +219 -0
- data/spec/fixtures/shiny-conf-json-old-name/skit1/preview.png +0 -0
- data/spec/fixtures/shiny-conf-yaml/.braids +8 -0
- data/spec/fixtures/shiny-conf-yaml/expected.braids.json +10 -0
- data/spec/fixtures/shiny-conf-yaml/skit1/layouts/layout.liquid +219 -0
- data/spec/fixtures/shiny-conf-yaml/skit1/preview.png +0 -0
- data/spec/fixtures/shiny/skit-layout.liquid.test +2 -0
- data/spec/fixtures/shiny/skit1.test +2 -0
- data/spec/fixtures/skit1.1x/layouts/layout.liquid +219 -0
- data/spec/integration/adding_spec.rb +82 -34
- data/spec/integration/config_versioning_spec.rb +222 -0
- data/spec/integration/diff_spec.rb +173 -9
- data/spec/integration/integration_helper.rb +23 -5
- data/spec/integration/push_spec.rb +57 -4
- data/spec/integration/remove_spec.rb +27 -5
- data/spec/integration/updating_spec.rb +104 -19
- metadata +73 -2
@@ -56,8 +56,6 @@ module Braid
|
|
56
56
|
rescue InvalidRevision
|
57
57
|
# Ignored as it means the revision matches expected
|
58
58
|
end
|
59
|
-
target_revision = determine_target_revision(mirror, new_revision)
|
60
|
-
current_revision = determine_target_revision(mirror, mirror.base_revision)
|
61
59
|
|
62
60
|
from_desc =
|
63
61
|
original_tag ? "tag '#{original_tag}'" :
|
@@ -77,7 +75,7 @@ module Braid
|
|
77
75
|
|
78
76
|
if !switching &&
|
79
77
|
(
|
80
|
-
(options['revision'] && was_locked &&
|
78
|
+
(options['revision'] && was_locked && new_revision == mirror.base_revision) ||
|
81
79
|
(options['revision'].nil? && !was_locked && mirror.merged?(git.rev_parse(new_revision)))
|
82
80
|
)
|
83
81
|
msg "Mirror '#{mirror.path}' is already up to date."
|
@@ -93,11 +91,13 @@ module Braid
|
|
93
91
|
in_error = false
|
94
92
|
begin
|
95
93
|
local_hash = git.rev_parse('HEAD')
|
96
|
-
base_hash = git.
|
97
|
-
|
94
|
+
base_hash = git.make_tree_with_item('HEAD', mirror.path,
|
95
|
+
mirror.upstream_item_for_revision(base_revision))
|
96
|
+
remote_hash = git.make_tree_with_item('HEAD', mirror.path,
|
97
|
+
mirror.upstream_item_for_revision(new_revision))
|
98
98
|
Operations::with_modified_environment({
|
99
99
|
"GITHEAD_#{local_hash}" => 'HEAD',
|
100
|
-
"GITHEAD_#{remote_hash}" =>
|
100
|
+
"GITHEAD_#{remote_hash}" => new_revision
|
101
101
|
}) do
|
102
102
|
git.merge_trees(base_hash, local_hash, remote_hash)
|
103
103
|
end
|
@@ -121,15 +121,6 @@ module Braid
|
|
121
121
|
msg "Updated mirror to #{display_revision(mirror)}."
|
122
122
|
clear_remote(mirror, options)
|
123
123
|
end
|
124
|
-
|
125
|
-
def generate_tree_hash(mirror, revision)
|
126
|
-
git.with_temporary_index do
|
127
|
-
git.read_tree_im('HEAD')
|
128
|
-
git.rm_r_cached(mirror.path)
|
129
|
-
git.read_tree_prefix_i(revision, mirror.path)
|
130
|
-
git.write_tree
|
131
|
-
end
|
132
|
-
end
|
133
124
|
end
|
134
125
|
end
|
135
126
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Braid
|
2
|
+
module Commands
|
3
|
+
class UpgradeConfig < Command
|
4
|
+
def config_mode
|
5
|
+
Config::MODE_UPGRADE
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(options)
|
9
|
+
# Config loading in MODE_UPGRADE will bail out only if the config
|
10
|
+
# version is too new.
|
11
|
+
|
12
|
+
if !config.config_existed
|
13
|
+
puts <<-MSG
|
14
|
+
Your repository has no Braid configuration file. It will be created with the
|
15
|
+
current configuration version when you add the first mirror.
|
16
|
+
MSG
|
17
|
+
return
|
18
|
+
elsif config.config_version == Config::CURRENT_CONFIG_VERSION
|
19
|
+
puts <<-MSG
|
20
|
+
Your configuration file is already at the current configuration version (#{Config::CURRENT_CONFIG_VERSION}).
|
21
|
+
MSG
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
puts <<-MSG
|
26
|
+
Your configuration file will be upgraded from configuration version #{config.config_version} to #{Config::CURRENT_CONFIG_VERSION}.
|
27
|
+
Other developers on your project will need to use a Braid version compatible
|
28
|
+
with configuration version #{Config::CURRENT_CONFIG_VERSION}; see
|
29
|
+
https://cristibalan.github.io/braid/config_versions.html .
|
30
|
+
|
31
|
+
MSG
|
32
|
+
|
33
|
+
unless config.breaking_change_descs.empty?
|
34
|
+
puts <<-MSG
|
35
|
+
The following breaking changes will occur:
|
36
|
+
#{config.breaking_change_descs.join('')}
|
37
|
+
MSG
|
38
|
+
end
|
39
|
+
|
40
|
+
if options['dry_run']
|
41
|
+
puts <<-MSG
|
42
|
+
Run 'braid upgrade-config#{config.breaking_change_descs.empty? ? '' : ' --allow-breaking-changes'}' to perform the upgrade.
|
43
|
+
MSG
|
44
|
+
elsif !config.breaking_change_descs.empty? && !options['allow_breaking_changes']
|
45
|
+
raise BraidError, 'You must pass --allow-breaking-changes to accept the breaking changes.'
|
46
|
+
else
|
47
|
+
config.write_db
|
48
|
+
add_config_file
|
49
|
+
had_changes = git.commit('Upgrade configuration')
|
50
|
+
raise InternalError, 'upgrade-config had no changes??' unless had_changes
|
51
|
+
msg 'Configuration upgrade complete.'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/braid/config.rb
CHANGED
@@ -2,8 +2,59 @@ require 'yaml'
|
|
2
2
|
require 'json'
|
3
3
|
require 'yaml/store'
|
4
4
|
|
5
|
+
# Some info about the configuration versioning design:
|
6
|
+
# https://github.com/cristibalan/braid/issues/66#issuecomment-354211311
|
7
|
+
#
|
8
|
+
# Current configuration format:
|
9
|
+
# ```
|
10
|
+
# {
|
11
|
+
# "config_version": 1,
|
12
|
+
# "mirrors": {
|
13
|
+
# <mirror path: string>: {
|
14
|
+
# "url": <upstream URL: string>,
|
15
|
+
# "path": <remote path: string>,
|
16
|
+
# "branch": <upstream branch: string>,
|
17
|
+
# "tag": <upstream tag: string>,
|
18
|
+
# "revision": <current upstream revision: string>
|
19
|
+
# }
|
20
|
+
# }
|
21
|
+
# }
|
22
|
+
# ```
|
23
|
+
#
|
24
|
+
# History of configuration formats understood by current Braid:
|
25
|
+
#
|
26
|
+
# - Braid 1.1.0, config_version 1:
|
27
|
+
# - "config_version" introduced; mirrors moved to "mirrors"
|
28
|
+
# - Single-file mirrors (f340b0c)
|
29
|
+
# - Braid 1.0.18:
|
30
|
+
# - Locked mirrors indicated by absence of "branch" and "tag" attributes, not
|
31
|
+
# presence of "lock" attribute (e6535aa)
|
32
|
+
# - Braid 1.0.17:
|
33
|
+
# - Support for full-history mirrors ("squashed": false) removed; "squashed"
|
34
|
+
# attribute no longer written (eb72030)
|
35
|
+
# - Braid 1.0.11:
|
36
|
+
# - "remote" attribute no longer written (f8fd088)
|
37
|
+
# - Braid 1.0.9:
|
38
|
+
# - .braids -> .braids.json (6806c61)
|
39
|
+
# - Braid 1.0.0:
|
40
|
+
# - YAML -> JSON (9d3fa11)
|
41
|
+
# - Support for Subversion mirrors removed ("type": "svn") removed (9d8d390)
|
42
|
+
#
|
43
|
+
#
|
44
|
+
# (Entries that predate the creation of this list have commit IDs for reference.
|
45
|
+
# Of course, when adding a new entry, you can't add the commit ID in the same
|
46
|
+
# commit, but you don't need to because people can just run `git log` on this
|
47
|
+
# file.)
|
48
|
+
|
5
49
|
module Braid
|
6
50
|
class Config
|
51
|
+
|
52
|
+
MODE_UPGRADE = 1
|
53
|
+
MODE_READ_ONLY = 2
|
54
|
+
MODE_MAY_WRITE = 3
|
55
|
+
|
56
|
+
CURRENT_CONFIG_VERSION = 1
|
57
|
+
|
7
58
|
class PathAlreadyInUse < BraidError
|
8
59
|
def message
|
9
60
|
"path already in use: #{super}"
|
@@ -15,25 +66,88 @@ module Braid
|
|
15
66
|
end
|
16
67
|
end
|
17
68
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
69
|
+
class RemoveMirrorDueToBreakingChange < StandardError
|
70
|
+
end
|
71
|
+
|
72
|
+
# For upgrade-config command only. XXX: Ideally would be immutable.
|
73
|
+
attr_reader :config_version, :config_existed, :breaking_change_descs
|
74
|
+
|
75
|
+
# options: config_file, old_config_files, mode
|
76
|
+
def initialize(options = {})
|
77
|
+
@config_file = options['config_file'] || CONFIG_FILE
|
78
|
+
old_config_files = options['old_config_files'] || [OLD_CONFIG_FILE]
|
79
|
+
@mode = options['mode'] || MODE_MAY_WRITE
|
80
|
+
|
81
|
+
data = load_config(@config_file, old_config_files)
|
82
|
+
@config_existed = !data.nil?
|
83
|
+
if !@config_existed
|
84
|
+
@config_version = CURRENT_CONFIG_VERSION
|
85
|
+
@db = {}
|
86
|
+
elsif data['config_version'].is_a?(Numeric)
|
87
|
+
@config_version = data['config_version']
|
88
|
+
@db = data['mirrors']
|
89
|
+
else
|
90
|
+
# Before config versioning (Braid < 1.1.0)
|
91
|
+
@config_version = 0
|
92
|
+
@db = data
|
93
|
+
end
|
94
|
+
|
95
|
+
if @config_version > CURRENT_CONFIG_VERSION
|
96
|
+
raise BraidError, <<-MSG
|
97
|
+
This version of Braid (#{VERSION}) is too old to understand your project's Braid
|
98
|
+
configuration file (version #{@config_version}). See the instructions at
|
99
|
+
https://cristibalan.github.io/braid/config_versions.html to install and use a
|
100
|
+
compatible newer version of Braid.
|
101
|
+
MSG
|
102
|
+
end
|
103
|
+
|
104
|
+
# In all modes, instantiate all mirrors to scan for breaking changes.
|
105
|
+
@breaking_change_descs = []
|
106
|
+
paths_to_delete = []
|
107
|
+
@db.each do |path, attributes|
|
22
108
|
begin
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
109
|
+
mirror = Mirror.new(path, attributes,
|
110
|
+
lambda {|desc| @breaking_change_descs.push(desc)})
|
111
|
+
# In MODE_UPGRADE, update @db now. In other modes, we won't write the
|
112
|
+
# config if an upgrade is needed, so it doesn't matter that we don't
|
113
|
+
# update @db.
|
114
|
+
#
|
115
|
+
# It's OK to change the values of existing keys during iteration:
|
116
|
+
# https://groups.google.com/d/msg/comp.lang.ruby/r5OI6UaxAAg/SVpU0cktmZEJ
|
117
|
+
write_mirror(mirror) if @mode == MODE_UPGRADE
|
118
|
+
rescue RemoveMirrorDueToBreakingChange
|
119
|
+
# I don't know if deleting during iteration is well-defined in all
|
120
|
+
# Ruby versions we support, so defer the deletion.
|
121
|
+
# ~ matt@mattmccutchen.net, 2017-12-31
|
122
|
+
paths_to_delete.push(path) if @mode == MODE_UPGRADE
|
34
123
|
end
|
35
124
|
end
|
36
|
-
|
125
|
+
paths_to_delete.each do |path|
|
126
|
+
@db.delete(path)
|
127
|
+
end
|
128
|
+
|
129
|
+
if @mode != MODE_UPGRADE && !@breaking_change_descs.empty?
|
130
|
+
raise BraidError, <<-MSG
|
131
|
+
This version of Braid (#{VERSION}) no longer supports a feature used by your
|
132
|
+
Braid configuration file (version #{@config_version}). Run 'braid upgrade-config --dry-run'
|
133
|
+
for information about upgrading your configuration file, or see the instructions
|
134
|
+
at https://cristibalan.github.io/braid/config_versions.html to install and run a
|
135
|
+
compatible older version of Braid.
|
136
|
+
MSG
|
137
|
+
end
|
138
|
+
|
139
|
+
if @mode == MODE_MAY_WRITE && @config_version < CURRENT_CONFIG_VERSION
|
140
|
+
raise BraidError, <<-MSG
|
141
|
+
This command may need to write to your Braid configuration file,
|
142
|
+
but this version of Braid (#{VERSION}) cannot write to your configuration file
|
143
|
+
(currently version #{config_version}) without upgrading it to configuration version #{CURRENT_CONFIG_VERSION},
|
144
|
+
which would force other developers on your project to upgrade Braid. Run
|
145
|
+
'braid upgrade-config' to proceed with the upgrade, or see the instructions at
|
146
|
+
https://cristibalan.github.io/braid/config_versions.html to install and run a
|
147
|
+
compatible older version of Braid.
|
148
|
+
MSG
|
149
|
+
end
|
150
|
+
|
37
151
|
end
|
38
152
|
|
39
153
|
def add_from_options(url, options)
|
@@ -62,6 +176,7 @@ module Braid
|
|
62
176
|
def add(mirror)
|
63
177
|
raise PathAlreadyInUse, mirror.path if get(mirror.path)
|
64
178
|
write_mirror(mirror)
|
179
|
+
write_db
|
65
180
|
end
|
66
181
|
|
67
182
|
def remove(mirror)
|
@@ -71,31 +186,55 @@ module Braid
|
|
71
186
|
|
72
187
|
def update(mirror)
|
73
188
|
raise MirrorDoesNotExist, mirror.path unless get(mirror.path)
|
74
|
-
@db.delete(mirror.path)
|
75
189
|
write_mirror(mirror)
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def write_mirror(mirror)
|
81
|
-
@db[mirror.path] = clean_attributes(mirror.attributes)
|
82
190
|
write_db
|
83
191
|
end
|
84
192
|
|
193
|
+
# Public for upgrade-config command only.
|
85
194
|
def write_db
|
86
195
|
new_db = {}
|
87
196
|
@db.keys.sort.each do |key|
|
88
|
-
new_db[key] =
|
89
|
-
|
90
|
-
new_db[key]
|
197
|
+
new_db[key] = {}
|
198
|
+
Braid::Mirror::ATTRIBUTES.each do |k|
|
199
|
+
new_db[key][k] = @db[key][k] if @db[key].has_key?(k)
|
91
200
|
end
|
92
201
|
end
|
202
|
+
new_data = {
|
203
|
+
'config_version' => CURRENT_CONFIG_VERSION,
|
204
|
+
'mirrors' => new_db
|
205
|
+
}
|
93
206
|
File.open(@config_file, 'wb') do |f|
|
94
|
-
f.write JSON.pretty_generate(
|
207
|
+
f.write JSON.pretty_generate(new_data)
|
95
208
|
f.write "\n"
|
96
209
|
end
|
97
210
|
end
|
98
211
|
|
212
|
+
private
|
213
|
+
|
214
|
+
def load_config(config_file, old_config_files)
|
215
|
+
(old_config_files + [config_file]).each do |file|
|
216
|
+
next unless File.exist?(file)
|
217
|
+
begin
|
218
|
+
store = YAML::Store.new(file)
|
219
|
+
data = {}
|
220
|
+
store.transaction(true) do
|
221
|
+
store.roots.each do |path|
|
222
|
+
data[path] = store[path]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
return data
|
226
|
+
rescue
|
227
|
+
data = JSON.parse(file)
|
228
|
+
return data if data
|
229
|
+
end
|
230
|
+
end
|
231
|
+
return nil
|
232
|
+
end
|
233
|
+
|
234
|
+
def write_mirror(mirror)
|
235
|
+
@db[mirror.path] = clean_attributes(mirror.attributes)
|
236
|
+
end
|
237
|
+
|
99
238
|
def clean_attributes(hash)
|
100
239
|
hash.reject { |k, v| v.nil? }
|
101
240
|
end
|
data/lib/braid/mirror.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
module Braid
|
2
2
|
class Mirror
|
3
|
-
|
3
|
+
# Since Braid 1.1.0, the attributes are written to .braids.json in this
|
4
|
+
# canonical order. For now, the order is chosen to match what Braid 1.0.22
|
5
|
+
# produced for newly added mirrors.
|
6
|
+
ATTRIBUTES = %w(url branch path tag revision)
|
4
7
|
|
5
8
|
class UnknownType < BraidError
|
6
9
|
def message
|
@@ -22,9 +25,44 @@ module Braid
|
|
22
25
|
|
23
26
|
attr_reader :path, :attributes
|
24
27
|
|
25
|
-
def initialize(path, attributes = {})
|
28
|
+
def initialize(path, attributes = {}, breaking_change_cb = DUMMY_BREAKING_CHANGE_CB)
|
26
29
|
@path = path.sub(/\/$/, '')
|
27
|
-
@attributes = attributes
|
30
|
+
@attributes = attributes.dup
|
31
|
+
|
32
|
+
# Not that it's terribly important to check for such an old feature. This
|
33
|
+
# is mainly to demonstrate the RemoveMirrorDueToBreakingChange mechanism
|
34
|
+
# in case we want to use it for something else in the future.
|
35
|
+
if !@attributes['type'].nil? && @attributes['type'] != 'git'
|
36
|
+
breaking_change_cb.call <<-DESC
|
37
|
+
- Mirror '#{path}' is of a Subversion repository, which is no
|
38
|
+
longer supported. The mirror will be removed from your configuration, leaving
|
39
|
+
the data in the tree.
|
40
|
+
DESC
|
41
|
+
raise Config::RemoveMirrorDueToBreakingChange
|
42
|
+
end
|
43
|
+
@attributes.delete('type')
|
44
|
+
|
45
|
+
# Migrate revision locks from Braid < 1.0.18. We no longer store the
|
46
|
+
# original branch or tag (the user has to specify it again when
|
47
|
+
# unlocking); we simply represent a locked revision by the absence of a
|
48
|
+
# branch or tag.
|
49
|
+
if @attributes['lock']
|
50
|
+
@attributes.delete('lock')
|
51
|
+
@attributes['branch'] = nil
|
52
|
+
@attributes['tag'] = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# Removal of support for full-history mirrors from Braid < 1.0.17 is a
|
56
|
+
# breaking change for users who wanted to use the imported history in some
|
57
|
+
# way.
|
58
|
+
if !@attributes['squashed'].nil? && @attributes['squashed'] != true
|
59
|
+
breaking_change_cb.call <<-DESC
|
60
|
+
- Mirror '#{path}' is full-history, which is no longer supported.
|
61
|
+
It will be changed to squashed. Upstream history already imported will remain
|
62
|
+
in your project's history and will have no effect on Braid.
|
63
|
+
DESC
|
64
|
+
end
|
65
|
+
@attributes.delete('squashed')
|
28
66
|
end
|
29
67
|
|
30
68
|
def self.new_from_options(url, options = {})
|
@@ -35,7 +73,7 @@ module Braid
|
|
35
73
|
tag = options['tag']
|
36
74
|
branch = options['branch'] || (tag.nil? ? 'master' : nil)
|
37
75
|
|
38
|
-
path = (options['path'] || extract_path_from_url(url)).sub(/\/$/, '')
|
76
|
+
path = (options['path'] || extract_path_from_url(url, options['remote_path'])).sub(/\/$/, '')
|
39
77
|
raise PathRequired unless path
|
40
78
|
|
41
79
|
remote_path = options['remote_path']
|
@@ -59,15 +97,68 @@ module Braid
|
|
59
97
|
!!base_revision && git.merge_base(commit, base_revision) == commit
|
60
98
|
end
|
61
99
|
|
62
|
-
def
|
63
|
-
|
100
|
+
def upstream_item_for_revision(revision)
|
101
|
+
git.get_tree_item(revision, self.remote_path)
|
64
102
|
end
|
65
103
|
|
104
|
+
# Return the arguments that should be passed to "git diff" to diff this
|
105
|
+
# mirror (including uncommitted changes by default), incorporating the given
|
106
|
+
# user-specified arguments. Having the caller run "git diff" is convenient
|
107
|
+
# for now but violates encapsulation a little; we may have to reorganize the
|
108
|
+
# code in order to add features.
|
109
|
+
def diff_args(user_args = [])
|
110
|
+
upstream_item = upstream_item_for_revision(base_revision)
|
111
|
+
|
112
|
+
# We do not need to spend the time to copy the content outside the
|
113
|
+
# mirror from HEAD because --relative will exclude it anyway. Rename
|
114
|
+
# detection seems to apply only to the files included in the diff, so we
|
115
|
+
# shouldn't have another bug like
|
116
|
+
# https://github.com/cristibalan/braid/issues/41.
|
117
|
+
base_tree = git.make_tree_with_item(nil, path, upstream_item)
|
118
|
+
|
119
|
+
# Note: --relative does a naive prefix comparison. If we set (for
|
120
|
+
# example) `--relative=a/b`, that will match an unrelated file or
|
121
|
+
# directory name `a/bb`. If the mirror is a directory, we can avoid this
|
122
|
+
# by adding a trailing slash to the prefix.
|
123
|
+
#
|
124
|
+
# If the mirror is a file, the only way we can avoid matching a path like
|
125
|
+
# `a/bb` is to pass a path argument to limit the diff. This means if the
|
126
|
+
# user passes additional path arguments, we won't get the behavior we
|
127
|
+
# expect, which is the intersection of the user-specified paths with the
|
128
|
+
# mirror. However, it's probably unreasonable for a user to pass path
|
129
|
+
# arguments when diffing a single-file mirror, so we ignore the issue.
|
130
|
+
#
|
131
|
+
# Note: This code doesn't handle various cases in which a directory at the
|
132
|
+
# root of a mirror turns into a file or vice versa. If that happens,
|
133
|
+
# hopefully the user takes corrective action manually.
|
134
|
+
if upstream_item.is_a?(git.BlobWithMode)
|
135
|
+
# For a single-file mirror, we use the upstream basename for the
|
136
|
+
# upstream side of the diff and the downstream basename for the
|
137
|
+
# downstream side, like what `git diff` does when given two blobs as
|
138
|
+
# arguments. Use --relative to strip away the entire downstream path
|
139
|
+
# before we add the basenames.
|
140
|
+
return [
|
141
|
+
'--relative=' + path,
|
142
|
+
'--src-prefix=a/' + File.basename(remote_path),
|
143
|
+
'--dst-prefix=b/' + File.basename(path),
|
144
|
+
base_tree,
|
145
|
+
# user_args may contain options, which must come before paths.
|
146
|
+
*user_args,
|
147
|
+
path
|
148
|
+
]
|
149
|
+
else
|
150
|
+
return [
|
151
|
+
'--relative=' + path + '/',
|
152
|
+
base_tree,
|
153
|
+
*user_args
|
154
|
+
]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Precondition: the remote for this mirror is set up.
|
66
159
|
def diff
|
67
160
|
fetch_base_revision_if_missing
|
68
|
-
|
69
|
-
local_hash = git.tree_hash(path)
|
70
|
-
remote_hash != local_hash ? git.diff_tree(remote_hash, local_hash) : ''
|
161
|
+
git.diff(diff_args)
|
71
162
|
end
|
72
163
|
|
73
164
|
# Re-fetching the remote after deleting and re-adding it may be slow even if
|
@@ -79,7 +170,7 @@ module Braid
|
|
79
170
|
begin
|
80
171
|
# Without ^{commit}, this will happily pass back an object hash even if
|
81
172
|
# the object isn't present. See the git-rev-parse(1) man page.
|
82
|
-
git.rev_parse(base_revision +
|
173
|
+
git.rev_parse(base_revision + '^{commit}')
|
83
174
|
rescue Operations::UnknownRevision
|
84
175
|
fetch
|
85
176
|
end
|
@@ -130,6 +221,11 @@ module Braid
|
|
130
221
|
|
131
222
|
private
|
132
223
|
|
224
|
+
DUMMY_BREAKING_CHANGE_CB = lambda { |desc|
|
225
|
+
raise InternalError, 'Instantiated a mirror using an unsupported ' +
|
226
|
+
'feature outside of configuration loading.'
|
227
|
+
}
|
228
|
+
|
133
229
|
def method_missing(name, *args)
|
134
230
|
if ATTRIBUTES.find { |attribute| name.to_s =~ /^(#{attribute})(=)?$/ }
|
135
231
|
if $2
|
@@ -159,7 +255,11 @@ module Braid
|
|
159
255
|
hash
|
160
256
|
end
|
161
257
|
|
162
|
-
def self.extract_path_from_url(url)
|
258
|
+
def self.extract_path_from_url(url, remote_path)
|
259
|
+
if remote_path
|
260
|
+
return File.basename(remote_path)
|
261
|
+
end
|
262
|
+
|
163
263
|
return nil unless url
|
164
264
|
name = File.basename(url)
|
165
265
|
|