braid 1.0.22 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|