Class: Braid::Mirror

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Includes:
Operations::VersionControl
Defined in:
lib/braid/mirror.rb

Defined Under Namespace

Classes: NoTagAndBranch, PathRequired, UnknownType

Constant Summary collapse

ATTRIBUTES =

Since Braid 1.1.0, the attributes are written to .braids.json in this canonical order. For now, the order is chosen to match what Braid 1.0.22 produced for newly added mirrors.

T.let(%w(url branch path tag revision), T::Array[String])
BreakingChangeCallback =
T.type_alias { T.proc.params(arg0: String).void }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from T::Sig

sig

Methods included from Operations::VersionControl

#git, #git_cache

Constructor Details

#initialize(path, attributes = {}, breaking_change_cb = DUMMY_BREAKING_CHANGE_CB) ⇒ Mirror

Returns a new instance of Mirror.



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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/braid/mirror.rb', line 42

def initialize(path, attributes = {}, breaking_change_cb = DUMMY_BREAKING_CHANGE_CB)
  @path       = T.let(path.sub(/\/$/, ''), String)
  @attributes = T.let(attributes.dup, T::Hash[String, T.untyped])

  # Not that it's terribly important to check for such an old feature.  This
  # is mainly to demonstrate the RemoveMirrorDueToBreakingChange mechanism
  # in case we want to use it for something else in the future.
  if !@attributes['type'].nil? && @attributes['type'] != 'git'
    breaking_change_cb.call <<-DESC
- Mirror '#{path}' is of a Subversion repository, which is no
  longer supported.  The mirror will be removed from your configuration, leaving
  the data in the tree.
DESC
    raise Config::RemoveMirrorDueToBreakingChange
  end
  @attributes.delete('type')

  # Migrate revision locks from Braid < 1.0.18.  We no longer store the
  # original branch or tag (the user has to specify it again when
  # unlocking); we simply represent a locked revision by the absence of a
  # branch or tag.
  if @attributes['lock']
    @attributes.delete('lock')
    @attributes['branch'] = nil
    @attributes['tag'] = nil
  end

  # Removal of support for full-history mirrors from Braid < 1.0.17 is a
  # breaking change for users who wanted to use the imported history in some
  # way.
  if !@attributes['squashed'].nil? && @attributes['squashed'] != true
    breaking_change_cb.call <<-DESC
- Mirror '#{path}' is full-history, which is no longer supported.
  It will be changed to squashed.  Upstream history already imported will remain
  in your project's history and will have no effect on Braid.
DESC
  end
  @attributes.delete('squashed')
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



37
38
39
# File 'lib/braid/mirror.rb', line 37

def attributes
  @attributes
end

#pathObject (readonly)

Returns the value of attribute path.



33
34
35
# File 'lib/braid/mirror.rb', line 33

def path
  @path
end

Class Method Details

.new_from_options(url, options = {}) ⇒ Object

Raises:



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/braid/mirror.rb', line 83

def self.new_from_options(url, options = {})
  url    = url.sub(/\/$/, '')

  raise NoTagAndBranch if options['tag'] && options['branch']

  tag = options['tag']
  branch = options['branch']

  path = (options['path'] || extract_path_from_url(url, options['remote_path'])).sub(/\/$/, '')
  raise PathRequired unless path

  remote_path = options['remote_path']

  attributes = {'url' => url, 'branch' => branch, 'path' => remote_path, 'tag' => tag}
  self.new(path, attributes)
end

Instance Method Details

#==(comparison) ⇒ Object



101
102
103
# File 'lib/braid/mirror.rb', line 101

def ==(comparison)
  path == comparison.path && attributes == comparison.attributes
end

#base_revisionObject



215
216
217
218
219
220
221
222
223
224
225
# File 'lib/braid/mirror.rb', line 215

def base_revision
  # Avoid a Sorbet "unreachable code" error.
  # TODO (typing): Is the revision expected to be non-nil nowadays?  Can we
  # just remove the `inferred_revision` code path now?
  nilable_revision = T.let(revision, T.nilable(String))
  if nilable_revision
    git.rev_parse(revision)
  else
    inferred_revision
  end
end

#branchObject



265
266
267
# File 'lib/braid/mirror.rb', line 265

def branch
  self.attributes['branch']
end

#branch=(new_value) ⇒ Object



270
271
272
# File 'lib/braid/mirror.rb', line 270

def branch=(new_value)
  self.attributes['branch'] = new_value
end

#cached?Boolean

Returns:

  • (Boolean)


210
211
212
# File 'lib/braid/mirror.rb', line 210

def cached?
  git.remote_url(remote) == cached_url
end

#cached_urlObject



307
308
309
# File 'lib/braid/mirror.rb', line 307

def cached_url
  git_cache.path(url)
end

#diffObject



182
183
184
185
# File 'lib/braid/mirror.rb', line 182

def diff
  fetch_base_revision_if_missing
  git.diff(diff_args)
end

#diff_args(user_args = []) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/braid/mirror.rb', line 131

def diff_args(user_args = [])
  upstream_item = upstream_item_for_revision(base_revision)

  # We do not need to spend the time to copy the content outside the
  # mirror from HEAD because --relative will exclude it anyway.  Rename
  # detection seems to apply only to the files included in the diff, so we
  # shouldn't have another bug like
  # https://github.com/cristibalan/braid/issues/41.
  base_tree = git.make_tree_with_item(nil, path, upstream_item)

  # Note: --relative does a naive prefix comparison.  If we set (for
  # example) `--relative=a/b`, that will match an unrelated file or
  # directory name `a/bb`.  If the mirror is a directory, we can avoid this
  # by adding a trailing slash to the prefix.
  #
  # If the mirror is a file, the only way we can avoid matching a path like
  # `a/bb` is to pass a path argument to limit the diff.  This means if the
  # user passes additional path arguments, we won't get the behavior we
  # expect, which is the intersection of the user-specified paths with the
  # mirror.  However, it's probably unreasonable for a user to pass path
  # arguments when diffing a single-file mirror, so we ignore the issue.
  #
  # Note: This code doesn't handle various cases in which a directory at the
  # root of a mirror turns into a file or vice versa.  If that happens,
  # hopefully the user takes corrective action manually.
  if upstream_item.is_a?(git.BlobWithMode)
    # For a single-file mirror, we use the upstream basename for the
    # upstream side of the diff and the downstream basename for the
    # downstream side, like what `git diff` does when given two blobs as
    # arguments.  Use --relative to strip away the entire downstream path
    # before we add the basenames.
    return [
      '--relative=' + path,
      '--src-prefix=a/' + File.basename(T.must(remote_path)),
      '--dst-prefix=b/' + File.basename(path),
      base_tree,
      # user_args may contain options, which must come before paths.
      *user_args,
      path
    ]
  else
    return [
      '--relative=' + path + '/',
      base_tree,
      *user_args
    ]
  end
end

#fetchObject



204
205
206
207
# File 'lib/braid/mirror.rb', line 204

def fetch
  git_cache.fetch(url) if cached?
  git.fetch(remote)
end

#fetch_base_revision_if_missingObject



193
194
195
196
197
198
199
200
201
# File 'lib/braid/mirror.rb', line 193

def fetch_base_revision_if_missing
  begin
    # Without ^{commit}, this will happily pass back an object hash even if
    # the object isn't present.  See the git-rev-parse(1) man page.
    git.rev_parse(base_revision + '^{commit}')
  rescue Operations::UnknownRevision
    fetch
  end
end

#local_refObject



228
229
230
231
232
233
# File 'lib/braid/mirror.rb', line 228

def local_ref
  return "#{self.remote}/#{self.branch}" unless self.branch.nil?
  return "tags/#{self.tag}" unless self.tag.nil?
  # TODO (typing): Remove this `T.must` if we make `revision` non-nilable.
  T.must(self.revision)
end

#locked?Boolean

Returns:

  • (Boolean)


106
107
108
# File 'lib/braid/mirror.rb', line 106

def locked?
  branch.nil? && tag.nil?
end

#merged?(commit) ⇒ Boolean

Returns:

  • (Boolean)


111
112
113
114
115
116
# File 'lib/braid/mirror.rb', line 111

def merged?(commit)
  # tip from spearce in #git:
  # `test z$(git merge-base A B) = z$(git rev-parse --verify A)`
  commit = git.rev_parse(commit)
  !!base_revision && git.merge_base(commit, base_revision) == commit
end

#remoteObject



312
313
314
# File 'lib/braid/mirror.rb', line 312

def remote
  "#{branch || tag || 'revision'}/braid/#{path}".gsub(/\/\./, '/_')
end

#remote_pathObject



275
276
277
# File 'lib/braid/mirror.rb', line 275

def remote_path
  self.attributes['path']
end

#remote_path=(remote_path) ⇒ Object



280
281
282
# File 'lib/braid/mirror.rb', line 280

def remote_path=(remote_path)
  self.attributes['path'] = remote_path
end

#remote_refObject



239
240
241
# File 'lib/braid/mirror.rb', line 239

def remote_ref
  self.branch.nil? ? "+refs/tags/#{self.tag}" : "+refs/heads/#{self.branch}"
end

#revisionObject



297
298
299
# File 'lib/braid/mirror.rb', line 297

def revision
  self.attributes['revision']
end

#revision=(new_value) ⇒ Object



302
303
304
# File 'lib/braid/mirror.rb', line 302

def revision=(new_value)
  self.attributes['revision'] = new_value
end

#tagObject



285
286
287
# File 'lib/braid/mirror.rb', line 285

def tag
  self.attributes['tag']
end

#tag=(new_value) ⇒ Object



290
291
292
# File 'lib/braid/mirror.rb', line 290

def tag=(new_value)
  self.attributes['tag'] = new_value
end

#upstream_item_for_revision(revision) ⇒ Object



121
122
123
# File 'lib/braid/mirror.rb', line 121

def upstream_item_for_revision(revision)
  git.get_tree_item(revision, self.remote_path)
end

#urlObject



255
256
257
# File 'lib/braid/mirror.rb', line 255

def url
  self.attributes['url']
end

#url=(new_value) ⇒ Object



260
261
262
# File 'lib/braid/mirror.rb', line 260

def url=(new_value)
  self.attributes['url'] = new_value
end