braid 1.1.9 → 1.1.10
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/lib/braid/command.rb +38 -15
- data/lib/braid/commands/add.rb +21 -11
- data/lib/braid/commands/diff.rb +27 -10
- data/lib/braid/commands/push.rb +27 -13
- data/lib/braid/commands/remove.rb +17 -4
- data/lib/braid/commands/setup.rb +12 -3
- data/lib/braid/commands/status.rb +17 -8
- data/lib/braid/commands/update.rb +47 -23
- data/lib/braid/commands/upgrade_config.rb +18 -4
- data/lib/braid/config.rb +28 -18
- data/lib/braid/main.rb +47 -33
- data/lib/braid/mirror.rb +40 -29
- data/lib/braid/operations.rb +10 -16
- data/lib/braid/operations_lite.rb +1 -1
- data/lib/braid/sorbet/fake_runtime.rb +19 -0
- data/lib/braid/version.rb +1 -1
- data/lib/braid.rb +2 -10
- metadata +49 -7
| @@ -1,12 +1,26 @@ | |
| 1 | 
            -
            # typed:  | 
| 1 | 
            +
            # typed: strict
         | 
| 2 2 | 
             
            module Braid
         | 
| 3 3 | 
             
              module Commands
         | 
| 4 4 | 
             
                class UpgradeConfig < Command
         | 
| 5 | 
            +
                  class Options < T::Struct
         | 
| 6 | 
            +
                    prop :dry_run, T::Boolean
         | 
| 7 | 
            +
                    prop :allow_breaking_changes, T::Boolean
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  sig {params(options: Options).void}
         | 
| 11 | 
            +
                  def initialize(options)
         | 
| 12 | 
            +
                    @options = options
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  private
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  sig {returns(Config::ConfigMode)}
         | 
| 5 18 | 
             
                  def config_mode
         | 
| 6 19 | 
             
                    Config::MODE_UPGRADE
         | 
| 7 20 | 
             
                  end
         | 
| 8 21 |  | 
| 9 | 
            -
                   | 
| 22 | 
            +
                  sig {void}
         | 
| 23 | 
            +
                  def run_internal
         | 
| 10 24 | 
             
                    # Config loading in MODE_UPGRADE will bail out only if the config
         | 
| 11 25 | 
             
                    # version is too new.
         | 
| 12 26 |  | 
| @@ -38,11 +52,11 @@ The following breaking changes will occur: | |
| 38 52 | 
             
            MSG
         | 
| 39 53 | 
             
                    end
         | 
| 40 54 |  | 
| 41 | 
            -
                    if options | 
| 55 | 
            +
                    if @options.dry_run
         | 
| 42 56 | 
             
                      puts <<-MSG
         | 
| 43 57 | 
             
            Run 'braid upgrade-config#{config.breaking_change_descs.empty? ? '' : ' --allow-breaking-changes'}' to perform the upgrade.
         | 
| 44 58 | 
             
            MSG
         | 
| 45 | 
            -
                    elsif !config.breaking_change_descs.empty? &&  | 
| 59 | 
            +
                    elsif !config.breaking_change_descs.empty? && !@options.allow_breaking_changes
         | 
| 46 60 | 
             
                      raise BraidError, 'You must pass --allow-breaking-changes to accept the breaking changes.'
         | 
| 47 61 | 
             
                    else
         | 
| 48 62 | 
             
                      config.write_db
         | 
    
        data/lib/braid/config.rb
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            # typed:  | 
| 1 | 
            +
            # typed: strict
         | 
| 2 2 | 
             
            require 'yaml'
         | 
| 3 3 | 
             
            require 'json'
         | 
| 4 4 | 
             
            require 'yaml/store'
         | 
| @@ -84,20 +84,29 @@ module Braid | |
| 84 84 | 
             
                sig {returns(T::Array[String])}
         | 
| 85 85 | 
             
                attr_reader :breaking_change_descs
         | 
| 86 86 |  | 
| 87 | 
            -
                 | 
| 88 | 
            -
                 | 
| 89 | 
            -
             | 
| 90 | 
            -
                   | 
| 91 | 
            -
                   | 
| 92 | 
            -
                  @mode            = options['mode']             || MODE_MAY_WRITE
         | 
| 87 | 
            +
                sig {params(config_file: String, old_config_files: T::Array[String], mode: ConfigMode).void}
         | 
| 88 | 
            +
                def initialize(config_file: CONFIG_FILE, old_config_files: [OLD_CONFIG_FILE], mode: MODE_MAY_WRITE)
         | 
| 89 | 
            +
                  @config_file     = config_file
         | 
| 90 | 
            +
                  old_config_files = old_config_files
         | 
| 91 | 
            +
                  @mode            = mode
         | 
| 93 92 |  | 
| 94 93 | 
             
                  data = load_config(@config_file, old_config_files)
         | 
| 95 | 
            -
                  @config_existed = !data.nil | 
| 94 | 
            +
                  @config_existed = T.let(!data.nil?, T::Boolean)
         | 
| 96 95 | 
             
                  if !@config_existed
         | 
| 97 | 
            -
                    @config_version = CURRENT_CONFIG_VERSION
         | 
| 98 | 
            -
                    @db = {}
         | 
| 96 | 
            +
                    @config_version = T.let(CURRENT_CONFIG_VERSION, Integer)
         | 
| 97 | 
            +
                    @db = T.let({}, T::Hash[String, Mirror::MirrorAttributes])
         | 
| 99 98 | 
             
                  elsif data['config_version'].is_a?(Numeric)
         | 
| 100 99 | 
             
                    @config_version = data['config_version']
         | 
| 100 | 
            +
                    # WARNING (typing): In general, slapping a type annotation on the loaded
         | 
| 101 | 
            +
                    # data without validating its structure could be misleading as we work
         | 
| 102 | 
            +
                    # on the rest of the code.  In the worst case, it might lead us to
         | 
| 103 | 
            +
                    # delete useful sanity checks because they trigger Sorbet unreachable
         | 
| 104 | 
            +
                    # code errors.  As of this writing (2024-08-04), we have nothing to lose
         | 
| 105 | 
            +
                    # because we don't attempt to defend against malformed data anyway, and
         | 
| 106 | 
            +
                    # there's a benefit to having the type information available.  If we add
         | 
| 107 | 
            +
                    # any validation, consider whether the annotations should be changed to
         | 
| 108 | 
            +
                    # verify that we did the validation correctly, e.g., by starting with a
         | 
| 109 | 
            +
                    # type like `T.anything` instead of `T.untyped`.
         | 
| 101 110 | 
             
                    @db = data['mirrors']
         | 
| 102 111 | 
             
                  else
         | 
| 103 112 | 
             
                    # Before config versioning (Braid < 1.1.0)
         | 
| @@ -115,7 +124,7 @@ MSG | |
| 115 124 | 
             
                  end
         | 
| 116 125 |  | 
| 117 126 | 
             
                  # In all modes, instantiate all mirrors to scan for breaking changes.
         | 
| 118 | 
            -
                  @breaking_change_descs = []
         | 
| 127 | 
            +
                  @breaking_change_descs = T.let([], T::Array[String])
         | 
| 119 128 | 
             
                  paths_to_delete = []
         | 
| 120 129 | 
             
                  @db.each do |path, attributes|
         | 
| 121 130 | 
             
                    begin
         | 
| @@ -163,7 +172,7 @@ MSG | |
| 163 172 |  | 
| 164 173 | 
             
                end
         | 
| 165 174 |  | 
| 166 | 
            -
                sig {params(url: String, options:  | 
| 175 | 
            +
                sig {params(url: String, options: Mirror::Options).returns(Mirror)}
         | 
| 167 176 | 
             
                def add_from_options(url, options)
         | 
| 168 177 | 
             
                  mirror = Mirror.new_from_options(url, options)
         | 
| 169 178 |  | 
| @@ -171,7 +180,7 @@ MSG | |
| 171 180 | 
             
                  mirror
         | 
| 172 181 | 
             
                end
         | 
| 173 182 |  | 
| 174 | 
            -
                sig {returns(T::Array[ | 
| 183 | 
            +
                sig {returns(T::Array[String])}
         | 
| 175 184 | 
             
                def mirrors
         | 
| 176 185 | 
             
                  @db.keys
         | 
| 177 186 | 
             
                end
         | 
| @@ -213,11 +222,12 @@ MSG | |
| 213 222 | 
             
                # Public for upgrade-config command only.
         | 
| 214 223 | 
             
                sig {void}
         | 
| 215 224 | 
             
                def write_db
         | 
| 216 | 
            -
                  new_db = {}
         | 
| 225 | 
            +
                  new_db = T.let({}, T::Hash[String, Mirror::MirrorAttributes])
         | 
| 217 226 | 
             
                  @db.keys.sort.each do |key|
         | 
| 218 | 
            -
                     | 
| 227 | 
            +
                    attrs = T.must(@db[key])
         | 
| 228 | 
            +
                    new_db[key] = new_attrs = {}
         | 
| 219 229 | 
             
                    Braid::Mirror::ATTRIBUTES.each do |k|
         | 
| 220 | 
            -
                       | 
| 230 | 
            +
                      new_attrs[k] = attrs[k] if attrs.has_key?(k)
         | 
| 221 231 | 
             
                    end
         | 
| 222 232 | 
             
                  end
         | 
| 223 233 | 
             
                  new_data = {
         | 
| @@ -237,7 +247,7 @@ MSG | |
| 237 247 | 
             
                  (old_config_files + [config_file]).each do |file|
         | 
| 238 248 | 
             
                    next unless File.exist?(file)
         | 
| 239 249 | 
             
                    begin
         | 
| 240 | 
            -
                      store = T. | 
| 250 | 
            +
                      store = T.unsafe(YAML::Store).new(file)
         | 
| 241 251 | 
             
                      data = {}
         | 
| 242 252 | 
             
                      store.transaction(true) do
         | 
| 243 253 | 
             
                        store.roots.each do |path|
         | 
| @@ -258,7 +268,7 @@ MSG | |
| 258 268 | 
             
                  @db[mirror.path] = clean_attributes(mirror.attributes)
         | 
| 259 269 | 
             
                end
         | 
| 260 270 |  | 
| 261 | 
            -
                sig {params(hash:  | 
| 271 | 
            +
                sig {params(hash: Mirror::MirrorAttributes).returns(Mirror::MirrorAttributes)}
         | 
| 262 272 | 
             
                def clean_attributes(hash)
         | 
| 263 273 | 
             
                  hash.reject { |_, v| v.nil? }
         | 
| 264 274 | 
             
                end
         | 
    
        data/lib/braid/main.rb
    CHANGED
    
    | @@ -1,3 +1,11 @@ | |
| 1 | 
            +
            # `typed: strict` doesn't seem worth the trouble for this file at this time:
         | 
| 2 | 
            +
            # there's not much for us to actually annotate, and it would take a crazy hack
         | 
| 3 | 
            +
            # to avoid a Sorbet error on the `@argv` references because Sorbet doesn't seem
         | 
| 4 | 
            +
            # to honor `T.bind` for instance variables (TODO: file a Sorbet bug about
         | 
| 5 | 
            +
            # that?).  Finding an approach to meaningfully check our code that uses the
         | 
| 6 | 
            +
            # `main` DSL is a bigger project that we may or may not undertake later.
         | 
| 7 | 
            +
            # ~ Matt 2024-08-04
         | 
| 8 | 
            +
            #
         | 
| 1 9 | 
             
            # typed: true
         | 
| 2 10 |  | 
| 3 11 | 
             
            require 'braid'
         | 
| @@ -31,8 +39,8 @@ T.unsafe(Main).run { | |
| 31 39 | 
             
              # The "main" library doesn't provide a way to do this??
         | 
| 32 40 | 
             
              def check_no_extra_args!
         | 
| 33 41 | 
             
                if @argv.length > 0
         | 
| 34 | 
            -
                   | 
| 35 | 
            -
                     | 
| 42 | 
            +
                  Command.handle_error(
         | 
| 43 | 
            +
                    BraidError.new('Extra argument(s) passed to command.'))
         | 
| 36 44 | 
             
                end
         | 
| 37 45 | 
             
              end
         | 
| 38 46 |  | 
| @@ -61,7 +69,7 @@ T.unsafe(Main).run { | |
| 61 69 | 
             
                run {
         | 
| 62 70 | 
             
                  check_no_extra_args!
         | 
| 63 71 | 
             
                  Braid.verbose = verbose
         | 
| 64 | 
            -
                   | 
| 72 | 
            +
                  Commands::Add.new(url, Mirror::Options.new(path: local_path, branch: branch, tag: tag, revision: revision, remote_path: path)).run
         | 
| 65 73 | 
             
                }
         | 
| 66 74 | 
             
              }
         | 
| 67 75 |  | 
| @@ -86,15 +94,15 @@ T.unsafe(Main).run { | |
| 86 94 |  | 
| 87 95 | 
             
                run {
         | 
| 88 96 | 
             
                  check_no_extra_args!
         | 
| 89 | 
            -
                  options =  | 
| 90 | 
            -
                     | 
| 91 | 
            -
                     | 
| 92 | 
            -
                     | 
| 93 | 
            -
                     | 
| 94 | 
            -
                     | 
| 95 | 
            -
                   | 
| 97 | 
            +
                  options = Commands::Update::Options.new(
         | 
| 98 | 
            +
                    branch: branch,
         | 
| 99 | 
            +
                    tag: tag,
         | 
| 100 | 
            +
                    revision: revision,
         | 
| 101 | 
            +
                    head: head,
         | 
| 102 | 
            +
                    keep: keep
         | 
| 103 | 
            +
                  )
         | 
| 96 104 | 
             
                  Braid.verbose = verbose
         | 
| 97 | 
            -
                   | 
| 105 | 
            +
                  Commands::Update.new(local_path, options).run
         | 
| 98 106 | 
             
                }
         | 
| 99 107 | 
             
              }
         | 
| 100 108 |  | 
| @@ -115,11 +123,11 @@ T.unsafe(Main).run { | |
| 115 123 |  | 
| 116 124 | 
             
                run {
         | 
| 117 125 | 
             
                  check_no_extra_args!
         | 
| 118 | 
            -
                  options =  | 
| 119 | 
            -
                    : | 
| 120 | 
            -
                   | 
| 126 | 
            +
                  options = Commands::Remove::Options.new(
         | 
| 127 | 
            +
                    keep: keep
         | 
| 128 | 
            +
                  )
         | 
| 121 129 | 
             
                  Braid.verbose = verbose
         | 
| 122 | 
            -
                   | 
| 130 | 
            +
                  Commands::Remove.new(local_path, options).run
         | 
| 123 131 | 
             
                }
         | 
| 124 132 | 
             
              }
         | 
| 125 133 |  | 
| @@ -141,12 +149,12 @@ T.unsafe(Main).run { | |
| 141 149 | 
             
                  if @argv.length > 0 && @argv[0] == '--'
         | 
| 142 150 | 
             
                    @argv.shift
         | 
| 143 151 | 
             
                  end
         | 
| 144 | 
            -
                  options =  | 
| 145 | 
            -
                     | 
| 146 | 
            -
                     | 
| 147 | 
            -
                   | 
| 152 | 
            +
                  options = Commands::Diff::Options.new(
         | 
| 153 | 
            +
                    keep: keep,
         | 
| 154 | 
            +
                    git_diff_args: @argv
         | 
| 155 | 
            +
                  )
         | 
| 148 156 | 
             
                  Braid.verbose = verbose
         | 
| 149 | 
            -
                   | 
| 157 | 
            +
                  Commands::Diff.new(local_path, options).run
         | 
| 150 158 | 
             
                }
         | 
| 151 159 | 
             
              }
         | 
| 152 160 |  | 
| @@ -159,12 +167,12 @@ T.unsafe(Main).run { | |
| 159 167 |  | 
| 160 168 | 
             
                run {
         | 
| 161 169 | 
             
                  check_no_extra_args!
         | 
| 162 | 
            -
                  options =  | 
| 163 | 
            -
                     | 
| 164 | 
            -
                     | 
| 165 | 
            -
                   | 
| 170 | 
            +
                  options = Commands::Push::Options.new(
         | 
| 171 | 
            +
                    keep: keep,
         | 
| 172 | 
            +
                    branch: branch
         | 
| 173 | 
            +
                  )
         | 
| 166 174 | 
             
                  Braid.verbose = verbose
         | 
| 167 | 
            -
                   | 
| 175 | 
            +
                  Commands::Push.new(local_path, options).run
         | 
| 168 176 | 
             
                }
         | 
| 169 177 | 
             
              }
         | 
| 170 178 |  | 
| @@ -179,7 +187,7 @@ T.unsafe(Main).run { | |
| 179 187 | 
             
                  check_no_extra_args!
         | 
| 180 188 | 
             
                  Braid.verbose = verbose
         | 
| 181 189 | 
             
                  Braid.force = force
         | 
| 182 | 
            -
                   | 
| 190 | 
            +
                  Commands::Setup.new(local_path).run
         | 
| 183 191 | 
             
                }
         | 
| 184 192 | 
             
              }
         | 
| 185 193 |  | 
| @@ -188,7 +196,7 @@ T.unsafe(Main).run { | |
| 188 196 |  | 
| 189 197 | 
             
                run {
         | 
| 190 198 | 
             
                  check_no_extra_args!
         | 
| 191 | 
            -
                  puts "braid #{ | 
| 199 | 
            +
                  puts "braid #{VERSION}"
         | 
| 192 200 | 
             
                }
         | 
| 193 201 | 
             
              }
         | 
| 194 202 |  | 
| @@ -200,7 +208,7 @@ T.unsafe(Main).run { | |
| 200 208 | 
             
                run {
         | 
| 201 209 | 
             
                  check_no_extra_args!
         | 
| 202 210 | 
             
                  Braid.verbose = verbose
         | 
| 203 | 
            -
                   | 
| 211 | 
            +
                  Commands::Status.new(local_path).run
         | 
| 204 212 | 
             
                }
         | 
| 205 213 | 
             
              }
         | 
| 206 214 |  | 
| @@ -220,6 +228,7 @@ T.unsafe(Main).run { | |
| 220 228 | 
             
                  optional
         | 
| 221 229 | 
             
                  desc 'Explain the consequences of the upgrade without performing it.'
         | 
| 222 230 | 
             
                  attr :dry_run
         | 
| 231 | 
            +
                  default false
         | 
| 223 232 | 
             
                }
         | 
| 224 233 |  | 
| 225 234 | 
             
                option('allow-breaking-changes') {
         | 
| @@ -228,16 +237,17 @@ T.unsafe(Main).run { | |
| 228 237 | 
             
                    Perform the upgrade even if it involves breaking changes.
         | 
| 229 238 | 
             
                  DESC
         | 
| 230 239 | 
             
                  attr :allow_breaking_changes
         | 
| 240 | 
            +
                  default false
         | 
| 231 241 | 
             
                }
         | 
| 232 242 |  | 
| 233 243 | 
             
                run {
         | 
| 234 244 | 
             
                  check_no_extra_args!
         | 
| 235 | 
            -
                  options =  | 
| 236 | 
            -
                     | 
| 237 | 
            -
                     | 
| 238 | 
            -
                   | 
| 245 | 
            +
                  options = Commands::UpgradeConfig::Options.new(
         | 
| 246 | 
            +
                    dry_run: dry_run,
         | 
| 247 | 
            +
                    allow_breaking_changes: allow_breaking_changes
         | 
| 248 | 
            +
                  )
         | 
| 239 249 | 
             
                  Braid.verbose = verbose
         | 
| 240 | 
            -
                   | 
| 250 | 
            +
                  Commands::UpgradeConfig.new(options).run
         | 
| 241 251 | 
             
                }
         | 
| 242 252 | 
             
              }
         | 
| 243 253 |  | 
| @@ -301,6 +311,7 @@ T.unsafe(Main).run { | |
| 301 311 | 
             
                  optional
         | 
| 302 312 | 
             
                  desc 'unused option'
         | 
| 303 313 | 
             
                  attr
         | 
| 314 | 
            +
                  default false
         | 
| 304 315 | 
             
                }
         | 
| 305 316 | 
             
              }
         | 
| 306 317 |  | 
| @@ -309,6 +320,7 @@ T.unsafe(Main).run { | |
| 309 320 | 
             
                  optional
         | 
| 310 321 | 
             
                  desc 'log shell commands'
         | 
| 311 322 | 
             
                  attr
         | 
| 323 | 
            +
                  default false
         | 
| 312 324 | 
             
                }
         | 
| 313 325 | 
             
              }
         | 
| 314 326 |  | 
| @@ -317,6 +329,7 @@ T.unsafe(Main).run { | |
| 317 329 | 
             
                  optional
         | 
| 318 330 | 
             
                  desc 'force'
         | 
| 319 331 | 
             
                  attr
         | 
| 332 | 
            +
                  default false
         | 
| 320 333 | 
             
                }
         | 
| 321 334 | 
             
              }
         | 
| 322 335 |  | 
| @@ -325,6 +338,7 @@ T.unsafe(Main).run { | |
| 325 338 | 
             
                  optional
         | 
| 326 339 | 
             
                  desc 'do not remove the remote'
         | 
| 327 340 | 
             
                  attr
         | 
| 341 | 
            +
                  default false
         | 
| 328 342 | 
             
                }
         | 
| 329 343 | 
             
              }
         | 
| 330 344 |  | 
    
        data/lib/braid/mirror.rb
    CHANGED
    
    | @@ -14,12 +14,6 @@ module Braid | |
| 14 14 | 
             
                    "unknown type: #{super}"
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 | 
             
                end
         | 
| 17 | 
            -
                class PathRequired < BraidError
         | 
| 18 | 
            -
                  sig {returns(String)}
         | 
| 19 | 
            -
                  def message
         | 
| 20 | 
            -
                    'path is required'
         | 
| 21 | 
            -
                  end
         | 
| 22 | 
            -
                end
         | 
| 23 17 | 
             
                class NoTagAndBranch < BraidError
         | 
| 24 18 | 
             
                  sig {returns(String)}
         | 
| 25 19 | 
             
                  def message
         | 
| @@ -32,16 +26,21 @@ module Braid | |
| 32 26 | 
             
                sig {returns(String)}
         | 
| 33 27 | 
             
                attr_reader :path
         | 
| 34 28 |  | 
| 35 | 
            -
                # It's going to take significant refactoring to be able to give | 
| 36 | 
            -
                 | 
| 29 | 
            +
                # It's going to take significant refactoring to be able to give
         | 
| 30 | 
            +
                # `MirrorAttributes` a type.  Encapsulating the `T.untyped` in a type alias
         | 
| 31 | 
            +
                # makes it easier to search for all the distinct root causes of untypedness
         | 
| 32 | 
            +
                # in the code.
         | 
| 33 | 
            +
                MirrorAttributes = T.type_alias { T::Hash[String, T.untyped] }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                sig {returns(MirrorAttributes)}
         | 
| 37 36 | 
             
                attr_reader :attributes
         | 
| 38 37 |  | 
| 39 38 | 
             
                BreakingChangeCallback = T.type_alias { T.proc.params(arg0: String).void }
         | 
| 40 39 |  | 
| 41 | 
            -
                sig {params(path: String, attributes:  | 
| 40 | 
            +
                sig {params(path: String, attributes: MirrorAttributes, breaking_change_cb: BreakingChangeCallback).void}
         | 
| 42 41 | 
             
                def initialize(path, attributes = {}, breaking_change_cb = DUMMY_BREAKING_CHANGE_CB)
         | 
| 43 42 | 
             
                  @path       = T.let(path.sub(/\/$/, ''), String)
         | 
| 44 | 
            -
                  @attributes = T.let(attributes.dup,  | 
| 43 | 
            +
                  @attributes = T.let(attributes.dup, MirrorAttributes)
         | 
| 45 44 |  | 
| 46 45 | 
             
                  # Not that it's terribly important to check for such an old feature.  This
         | 
| 47 46 | 
             
                  # is mainly to demonstrate the RemoveMirrorDueToBreakingChange mechanism
         | 
| @@ -79,19 +78,39 @@ DESC | |
| 79 78 | 
             
                  @attributes.delete('squashed')
         | 
| 80 79 | 
             
                end
         | 
| 81 80 |  | 
| 82 | 
            -
                 | 
| 83 | 
            -
                 | 
| 81 | 
            +
                # `Mirror::Options` doubles as the options struct for the `add` command, so
         | 
| 82 | 
            +
                # some properties are meaningful only in that context.  TODO: Maybe the code
         | 
| 83 | 
            +
                # could be organized in a better way.
         | 
| 84 | 
            +
                class Options < T::Struct
         | 
| 85 | 
            +
                  prop :tag, T.nilable(String)
         | 
| 86 | 
            +
                  prop :branch, T.nilable(String)
         | 
| 87 | 
            +
                  # NOTE: Used only by the `add` command; ignored by
         | 
| 88 | 
            +
                  # `Mirror::new_from_options`.
         | 
| 89 | 
            +
                  prop :revision, T.nilable(Operations::Git::ObjectExpr)
         | 
| 90 | 
            +
                  prop :path, T.nilable(String)
         | 
| 91 | 
            +
                  prop :remote_path, T.nilable(String)
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                sig {params(url: String, options: Options).returns(Mirror)}
         | 
| 95 | 
            +
                def self.new_from_options(url, options = Options.new)
         | 
| 84 96 | 
             
                  url    = url.sub(/\/$/, '')
         | 
| 97 | 
            +
                  # TODO: Ensure `url` is absolute?  The user is probably more likely to
         | 
| 98 | 
            +
                  # move the downstream repository by itself than to move it along with the
         | 
| 99 | 
            +
                  # vendor repository.  And we definitely don't want to use relative URLs in
         | 
| 100 | 
            +
                  # the cache.
         | 
| 85 101 |  | 
| 86 | 
            -
                  raise NoTagAndBranch if options | 
| 102 | 
            +
                  raise NoTagAndBranch if options.tag && options.branch
         | 
| 87 103 |  | 
| 88 | 
            -
                  tag = options | 
| 89 | 
            -
                  branch = options | 
| 104 | 
            +
                  tag = options.tag
         | 
| 105 | 
            +
                  branch = options.branch
         | 
| 90 106 |  | 
| 91 | 
            -
                  path = (options | 
| 92 | 
            -
                   | 
| 107 | 
            +
                  path = (options.path || extract_path_from_url(url, options.remote_path)).sub(/\/$/, '')
         | 
| 108 | 
            +
                  # TODO: Check that `path` is a valid relative path and not something like
         | 
| 109 | 
            +
                  # '.' or ''.  Some of these pathological cases will cause Braid to bail
         | 
| 110 | 
            +
                  # out later when something else fails, but it would be better to check up
         | 
| 111 | 
            +
                  # front.
         | 
| 93 112 |  | 
| 94 | 
            -
                  remote_path = options | 
| 113 | 
            +
                  remote_path = options.remote_path
         | 
| 95 114 |  | 
| 96 115 | 
             
                  attributes = {'url' => url, 'branch' => branch, 'path' => remote_path, 'tag' => tag}
         | 
| 97 116 | 
             
                  self.new(path, attributes)
         | 
| @@ -353,24 +372,16 @@ DESC | |
| 353 372 | 
             
                  hash
         | 
| 354 373 | 
             
                end
         | 
| 355 374 |  | 
| 356 | 
            -
                 | 
| 357 | 
            -
                sig {params(url: String, remote_path: T.nilable(String)).returns(T.nilable(String))}
         | 
| 375 | 
            +
                sig {params(url: String, remote_path: T.nilable(String)).returns(String)}
         | 
| 358 376 | 
             
                def self.extract_path_from_url(url, remote_path)
         | 
| 359 377 | 
             
                  if remote_path
         | 
| 360 378 | 
             
                    return File.basename(remote_path)
         | 
| 361 379 | 
             
                  end
         | 
| 362 380 |  | 
| 363 | 
            -
                  # Avoid a Sorbet "unreachable code" error.
         | 
| 364 | 
            -
                  # TODO (typing): Fix this properly.  Probably just remove this line?
         | 
| 365 | 
            -
                  return nil unless T.let(url, T.nilable(String))
         | 
| 366 381 | 
             
                  name = File.basename(url)
         | 
| 367 382 |  | 
| 368 | 
            -
                   | 
| 369 | 
            -
             | 
| 370 | 
            -
                    name[0..-5]
         | 
| 371 | 
            -
                  else
         | 
| 372 | 
            -
                    name
         | 
| 373 | 
            -
                  end
         | 
| 383 | 
            +
                  # strip .git if present
         | 
| 384 | 
            +
                  name.delete_suffix('.git')
         | 
| 374 385 | 
             
                end
         | 
| 375 386 | 
             
              end
         | 
| 376 387 | 
             
            end
         | 
    
        data/lib/braid/operations.rb
    CHANGED
    
    | @@ -10,26 +10,18 @@ module Braid | |
| 10 10 |  | 
| 11 11 | 
             
              module Operations
         | 
| 12 12 | 
             
                class ShellExecutionError < BraidError
         | 
| 13 | 
            -
                   | 
| 14 | 
            -
                  sig {returns(T.nilable(String))}
         | 
| 13 | 
            +
                  sig {returns(String)}
         | 
| 15 14 | 
             
                  attr_reader :err, :out
         | 
| 16 15 |  | 
| 17 | 
            -
                  sig {params(err:  | 
| 18 | 
            -
                  def initialize(err | 
| 16 | 
            +
                  sig {params(err: String, out: String).void}
         | 
| 17 | 
            +
                  def initialize(err, out)
         | 
| 19 18 | 
             
                    @err = err
         | 
| 20 19 | 
             
                    @out = out
         | 
| 21 20 | 
             
                  end
         | 
| 22 21 |  | 
| 23 22 | 
             
                  sig {returns(String)}
         | 
| 24 23 | 
             
                  def message
         | 
| 25 | 
            -
                     | 
| 26 | 
            -
                    # Currently, first_line can be nil if @err was empty, but Sorbet thinks
         | 
| 27 | 
            -
                    # that the `message` method of an Exception should always return non-nil
         | 
| 28 | 
            -
                    # (although override checking isn't enforced as of this writing), so
         | 
| 29 | 
            -
                    # handle nil here.  This seems ad-hoc but better than putting in a
         | 
| 30 | 
            -
                    # `T.must` that we know has a risk of being wrong.  Hopefully this will
         | 
| 31 | 
            -
                    # be fixed better in https://github.com/cristibalan/braid/issues/90.
         | 
| 32 | 
            -
                    first_line.nil? ? '' : first_line
         | 
| 24 | 
            +
                    @err
         | 
| 33 25 | 
             
                  end
         | 
| 34 26 | 
             
                end
         | 
| 35 27 | 
             
                class VersionTooLow < BraidError
         | 
| @@ -201,7 +193,9 @@ module Braid | |
| 201 193 | 
             
                  ObjectID = T.type_alias { String }
         | 
| 202 194 |  | 
| 203 195 | 
             
                  # A string containing an expression that can be evaluated to an object ID
         | 
| 204 | 
            -
                  # by `git rev-parse`.   | 
| 196 | 
            +
                  # by `git rev-parse`.  This type is enforced even less than `ObjectID` (in
         | 
| 197 | 
            +
                  # some cases, we apply it directly to user input without validation), but
         | 
| 198 | 
            +
                  # it still serves to document our intent.
         | 
| 205 199 | 
             
                  ObjectExpr = T.type_alias { String } 
         | 
| 206 200 |  | 
| 207 201 | 
             
                  # Get the physical path to a file in the git repository (e.g.,
         | 
| @@ -252,7 +246,7 @@ module Braid | |
| 252 246 | 
             
                    elsif out.match(/nothing.* to commit/)
         | 
| 253 247 | 
             
                      false
         | 
| 254 248 | 
             
                    else
         | 
| 255 | 
            -
                      raise ShellExecutionError,  | 
| 249 | 
            +
                      raise ShellExecutionError.new(err, out)
         | 
| 256 250 | 
             
                    end
         | 
| 257 251 | 
             
                  end
         | 
| 258 252 |  | 
| @@ -364,7 +358,7 @@ module Braid | |
| 364 358 | 
             
                        # This can happen if the user runs `braid add` with a `--path` that
         | 
| 365 359 | 
             
                        # doesn't exist.  TODO: Make the error message more user-friendly in
         | 
| 366 360 | 
             
                        # that case.
         | 
| 367 | 
            -
                        raise  | 
| 361 | 
            +
                        raise BraidError, 'No tree item exists at the given path'
         | 
| 368 362 | 
             
                      end
         | 
| 369 363 | 
             
                      mode = T.must(m[1])
         | 
| 370 364 | 
             
                      type = T.must(m[2])
         | 
| @@ -374,7 +368,7 @@ module Braid | |
| 374 368 | 
             
                      elsif type == 'blob'
         | 
| 375 369 | 
             
                        return BlobWithMode.new(hash, mode)
         | 
| 376 370 | 
             
                      else
         | 
| 377 | 
            -
                        raise  | 
| 371 | 
            +
                        raise BraidError, 'Tree item is not a tree or a blob'
         | 
| 378 372 | 
             
                      end
         | 
| 379 373 | 
             
                    end
         | 
| 380 374 | 
             
                  end
         | 
| @@ -22,7 +22,7 @@ module Braid | |
| 22 22 | 
             
                  ).returns(T.type_parameter(:R))
         | 
| 23 23 | 
             
                }
         | 
| 24 24 | 
             
                def self.with_modified_environment(dict, &blk)
         | 
| 25 | 
            -
                  orig_dict = {}
         | 
| 25 | 
            +
                  orig_dict = T.let({}, T::Hash[String, T.nilable(String)])
         | 
| 26 26 | 
             
                  dict.each { |name, value|
         | 
| 27 27 | 
             
                    orig_dict[name] = ENV[name]
         | 
| 28 28 | 
             
                    ENV[name] = value
         | 
| @@ -74,5 +74,24 @@ module Braid | |
| 74 74 | 
             
                  end
         | 
| 75 75 | 
             
                end
         | 
| 76 76 |  | 
| 77 | 
            +
                class Struct
         | 
| 78 | 
            +
                  def initialize(**kwargs)
         | 
| 79 | 
            +
                    # The fake runtime isn't obliged to validate the property names or
         | 
| 80 | 
            +
                    # types.
         | 
| 81 | 
            +
                    #
         | 
| 82 | 
            +
                    # Note: If the caller passed a hash of keyword arguments, Ruby will copy
         | 
| 83 | 
            +
                    # it, so we don't need to copy `kwargs` again here to avoid aliasing.
         | 
| 84 | 
            +
                    @attrs = kwargs
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  def self.prop(prop_name, prop_type)
         | 
| 88 | 
            +
                    define_method(prop_name) {
         | 
| 89 | 
            +
                      @attrs[prop_name]
         | 
| 90 | 
            +
                    }
         | 
| 91 | 
            +
                    define_method(:"#{prop_name}=") { |new_value|
         | 
| 92 | 
            +
                      @attrs[prop_name] = new_value
         | 
| 93 | 
            +
                    }
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 77 96 | 
             
              end
         | 
| 78 97 | 
             
            end
         | 
    
        data/lib/braid/version.rb
    CHANGED
    
    
    
        data/lib/braid.rb
    CHANGED
    
    | @@ -27,10 +27,7 @@ module Braid | |
| 27 27 | 
             
                !!@verbose
         | 
| 28 28 | 
             
              end
         | 
| 29 29 |  | 
| 30 | 
            -
               | 
| 31 | 
            -
              # apparently `lib/braid/main.rb` passes nil sometimes. Is that easy to fix?
         | 
| 32 | 
            -
              # (Ditto with `self.force=` below.)
         | 
| 33 | 
            -
              sig {params(new_value: T.nilable(T::Boolean)).void}
         | 
| 30 | 
            +
              sig {params(new_value: T::Boolean).void}
         | 
| 34 31 | 
             
              def self.verbose=(new_value)
         | 
| 35 32 | 
             
                @verbose = !!new_value
         | 
| 36 33 | 
             
              end
         | 
| @@ -42,7 +39,7 @@ module Braid | |
| 42 39 | 
             
                !!@force
         | 
| 43 40 | 
             
              end
         | 
| 44 41 |  | 
| 45 | 
            -
              sig {params(new_value: T | 
| 42 | 
            +
              sig {params(new_value: T::Boolean).void}
         | 
| 46 43 | 
             
              def self.force=(new_value)
         | 
| 47 44 | 
             
                @force = !!new_value
         | 
| 48 45 | 
             
              end
         | 
| @@ -59,11 +56,6 @@ module Braid | |
| 59 56 |  | 
| 60 57 | 
             
              class BraidError < StandardError
         | 
| 61 58 | 
             
                extend T::Sig
         | 
| 62 | 
            -
                sig {returns(String)}
         | 
| 63 | 
            -
                def message
         | 
| 64 | 
            -
                  value = super
         | 
| 65 | 
            -
                  value if value != self.class.name
         | 
| 66 | 
            -
                end
         | 
| 67 59 | 
             
              end
         | 
| 68 60 |  | 
| 69 61 | 
             
              class InternalError < BraidError
         |