Commits

bbinet committed c26f074

add support of schemas for autogenerate

for some reasons one of the new tests fails

Comments (0)

Files changed (2)

alembic/autogenerate.py

             Table(u'bar', MetaData(bind=None),
                 Column(u'data', VARCHAR(), table=<bar>), schema=None)),
           ( 'add_column',
+            None,
             'foo',
             Column('data', Integer(), table=<foo>)),
           ( 'remove_column',
+            None,
             'foo',
             Column(u'old_data', VARCHAR(), table=None)),
           [ ( 'modify_nullable',
+              None,
               'foo',
               u'x',
               { 'existing_server_default': None,
                             include_symbol=None):
     inspector = Inspector.from_engine(connection)
     # TODO: not hardcode alembic_version here ?
-    conn_table_names = set(inspector.get_table_names()).\
-                            difference(['alembic_version'])
+    conn_table_names = set()
+    schemas = inspector.get_schema_names() or [None]
+    for s in schemas:
+        if s == 'information_schema':
+            # ignore postgres own information_schema
+            continue
+        tables = set(inspector.get_table_names(schema=s)).\
+                difference(['alembic_version'])
+        conn_table_names.update(zip([s] * len(tables), tables))
 
-
-    metadata_table_names = OrderedSet([table.name
+    metadata_table_names = OrderedSet([(table.schema, table.name)
                                 for table in metadata.sorted_tables])
 
     if include_symbol:
-        conn_table_names = set(name for name in conn_table_names
-                            if include_symbol(name))
-        metadata_table_names = OrderedSet(name for name in metadata_table_names
-                                if include_symbol(name))
+        conn_table_names = set((s, name)
+                                for s, name in conn_table_names
+                                if include_symbol(name, schema=s))
+        metadata_table_names = OrderedSet((s, name)
+                                for s, name in metadata_table_names
+                                if include_symbol(name, schema=s))
 
     _compare_tables(conn_table_names, metadata_table_names,
                     inspector, metadata, diffs, autogen_context)
 
 def _compare_tables(conn_table_names, metadata_table_names,
                     inspector, metadata, diffs, autogen_context):
-    for tname in metadata_table_names.difference(conn_table_names):
-        diffs.append(("add_table", metadata.tables[tname]))
-        log.info("Detected added table %r", tname)
+    for s, tname in metadata_table_names.difference(conn_table_names):
+        name = '%s.%s' % (s, tname) if s else tname
+        diffs.append(("add_table", metadata.tables[name]))
+        log.info("Detected added table %r", name)
 
     removal_metadata = sa_schema.MetaData()
-    for tname in conn_table_names.difference(metadata_table_names):
-        exists = tname in removal_metadata.tables
-        t = sa_schema.Table(tname, removal_metadata)
+    for s, tname in conn_table_names.difference(metadata_table_names):
+        name = '%s.%s' % (s, tname) if s else tname
+        exists = name in removal_metadata.tables
+        t = sa_schema.Table(tname, removal_metadata, schema=s)
         if not exists:
             inspector.reflecttable(t, None)
         diffs.append(("remove_table", t))
-        log.info("Detected removed table %r", tname)
+        log.info("Detected removed table %r", name)
 
     existing_tables = conn_table_names.intersection(metadata_table_names)
 
     conn_column_info = dict(
-        (tname,
+        ((s, tname),
             dict(
                 (rec["name"], rec)
-                for rec in inspector.get_columns(tname)
+                for rec in inspector.get_columns(tname, schema=s)
             )
         )
-        for tname in existing_tables
+        for s, tname in existing_tables
     )
 
-    for tname in sorted(existing_tables):
-        _compare_columns(tname,
-                conn_column_info[tname],
-                metadata.tables[tname],
+    for s, tname in sorted(existing_tables):
+        name = '%s.%s' % (s, tname) if s else tname
+        _compare_columns(s, tname,
+                conn_column_info[(s, tname)],
+                metadata.tables[name],
                 diffs, autogen_context)
 
     # TODO:
 ###################################################
 # element comparison
 
-def _compare_columns(tname, conn_table, metadata_table,
+def _compare_columns(schema, tname, conn_table, metadata_table,
                                 diffs, autogen_context):
+    name = '%s.%s' % (schema, tname) if schema else tname
     metadata_cols_by_name = dict((c.name, c) for c in metadata_table.c)
     conn_col_names = set(conn_table)
     metadata_col_names = set(metadata_cols_by_name)
 
     for cname in metadata_col_names.difference(conn_col_names):
         diffs.append(
-            ("add_column", tname, metadata_cols_by_name[cname])
+            ("add_column", schema, tname, metadata_cols_by_name[cname])
         )
-        log.info("Detected added column '%s.%s'", tname, cname)
+        log.info("Detected added column '%s.%s'", name, cname)
 
     for cname in conn_col_names.difference(metadata_col_names):
         diffs.append(
-            ("remove_column", tname, sa_schema.Column(
+            ("remove_column", schema, tname, sa_schema.Column(
                 cname,
                 conn_table[cname]['type'],
                 nullable=conn_table[cname]['nullable'],
                 server_default=conn_table[cname]['default']
             ))
         )
-        log.info("Detected removed column '%s.%s'", tname, cname)
+        log.info("Detected removed column '%s.%s'", name, cname)
 
     for colname in metadata_col_names.intersection(conn_col_names):
         metadata_col = metadata_table.c[colname]
         conn_col = conn_table[colname]
         col_diff = []
-        _compare_type(tname, colname,
+        _compare_type(schema, tname, colname,
             conn_col,
             metadata_col,
             col_diff, autogen_context
         )
-        _compare_nullable(tname, colname,
+        _compare_nullable(schema, tname, colname,
             conn_col,
             metadata_col.nullable,
             col_diff, autogen_context
         )
-        _compare_server_default(tname, colname,
+        _compare_server_default(schema, tname, colname,
             conn_col,
             metadata_col,
             col_diff, autogen_context
         if col_diff:
             diffs.append(col_diff)
 
-def _compare_nullable(tname, cname, conn_col,
+def _compare_nullable(schema, tname, cname, conn_col,
                             metadata_col_nullable, diffs,
                             autogen_context):
     conn_col_nullable = conn_col['nullable']
     if conn_col_nullable is not metadata_col_nullable:
         diffs.append(
-            ("modify_nullable", tname, cname,
+            ("modify_nullable", schema, tname, cname,
                 {
                     "existing_type": conn_col['type'],
                     "existing_server_default": conn_col['default'],
             cname
         )
 
-def _compare_type(tname, cname, conn_col,
+def _compare_type(schema, tname, cname, conn_col,
                             metadata_col, diffs,
                             autogen_context):
 
     if isdiff:
 
         diffs.append(
-            ("modify_type", tname, cname,
+            ("modify_type", schema, tname, cname,
                     {
                         "existing_nullable": conn_col['nullable'],
                         "existing_server_default": conn_col['default'],
             conn_type, metadata_type, tname, cname
         )
 
-def _compare_server_default(tname, cname, conn_col, metadata_col,
+def _compare_server_default(schema, tname, cname, conn_col, metadata_col,
                                 diffs, autogen_context):
 
     metadata_default = metadata_col.server_default
     if isdiff:
         conn_col_default = conn_col['default']
         diffs.append(
-            ("modify_default", tname, cname,
+            ("modify_default", schema, tname, cname,
                 {
                     "existing_nullable": conn_col['nullable'],
                     "existing_type": conn_col['type'],
         return cmd_callables[0](*cmd_args)
 
 def _invoke_modify_command(updown, args, autogen_context):
-    tname, cname = args[0][1:3]
+    sname, tname, cname = args[0][1:4]
     kw = {}
 
     _arg_struct = {
         "modify_default": ("existing_server_default", "server_default"),
     }
     for diff in args:
-        diff_kw = diff[3]
+        diff_kw = diff[4]
         for arg in ("existing_type", \
                 "existing_nullable", \
                 "existing_server_default"):
         kw.pop("existing_nullable", None)
     if "server_default" in kw:
         kw.pop("existing_server_default", None)
-    return _modify_col(tname, cname, autogen_context, **kw)
+    return _modify_col(tname, cname, autogen_context, schema=sname, **kw)
 
 ###################################################
 # render python
 
 def _add_table(table, autogen_context):
-    return "%(prefix)screate_table(%(tablename)r,\n%(args)s\n)" % {
+    text = "%(prefix)screate_table(%(tablename)r,\n%(args)s" % {
         'tablename': table.name,
         'prefix': _alembic_autogenerate_prefix(autogen_context),
         'args': ',\n'.join(
                     table.constraints]
                 if rcons is not None
             ])
-        ),
+        )
     }
+    if table.schema:
+        text += ",\nschema=%r" % table.schema
+    text += "\n)"
+    return text
 
 def _drop_table(table, autogen_context):
-    return "%(prefix)sdrop_table(%(tname)r)" % {
+    text = "%(prefix)sdrop_table(%(tname)r" % {
             "prefix": _alembic_autogenerate_prefix(autogen_context),
             "tname": table.name
         }
+    if table.schema:
+        text += ", schema=%r" % table.schema
+    text += ")"
+    return text
 
-def _add_column(tname, column, autogen_context):
-    return "%(prefix)sadd_column(%(tname)r, %(column)s)" % {
+def _add_column(schema, tname, column, autogen_context):
+    text = "%(prefix)sadd_column(%(tname)r, %(column)s" % {
             "prefix": _alembic_autogenerate_prefix(autogen_context),
             "tname": tname,
             "column": _render_column(column, autogen_context)
             }
+    if schema:
+        text += ", schema=%r" % schema
+    text += ")"
+    return text
 
-def _drop_column(tname, column, autogen_context):
-    return "%(prefix)sdrop_column(%(tname)r, %(cname)r)" % {
+def _drop_column(schema, tname, column, autogen_context):
+    text = "%(prefix)sdrop_column(%(tname)r, %(cname)r" % {
             "prefix": _alembic_autogenerate_prefix(autogen_context),
             "tname": tname,
             "cname": column.name
             }
+    if schema:
+        text += ", schema=%r" % schema
+    text += ")"
+    return text
 
 def _modify_col(tname, cname,
                 autogen_context,
                 nullable=None,
                 existing_type=None,
                 existing_nullable=None,
-                existing_server_default=False):
+                existing_server_default=False,
+                schema=None):
     sqla_prefix = _sqlalchemy_autogenerate_prefix(autogen_context)
     indent = " " * 11
     text = "%(prefix)salter_column(%(tname)r, %(cname)r" % {
                             existing_server_default,
                             autogen_context),
                     )
+    if schema:
+        text += ",\n%sschema=%r" % (indent, schema)
     text += ")"
     return text
 

tests/test_autogenerate.py

 import sys
 py3k = sys.version_info >= (3, )
 
-def _model_one():
-    m = MetaData()
+def _model_one(schema=None):
+    m = MetaData(schema=schema)
 
     Table('user', m,
         Column('id', Integer, primary_key=True),
 
     return m
 
-def _model_two():
-    m = MetaData()
+def _model_two(schema=None):
+    m = MetaData(schema=schema)
 
     Table('user', m,
         Column('id', Integer, primary_key=True),
         template_args = {}
         autogenerate._produce_migration_diffs(self.context,
             template_args, set(),
-            include_symbol=lambda name: name == 'sometable')
+            include_symbol=lambda name, schema=None: name == 'sometable')
         eq_(
             re.sub(r"u'", "'", template_args['upgrades']),
             "### commands auto generated by Alembic - please adjust! ###\n"
 
 
 
+class AutogenerateDiffTestWSchema(AutogenTest, TestCase):
+
+    @classmethod
+    def _get_bind(cls):
+        return db_for_dialect('postgresql')
+
+    @classmethod
+    def _get_db_schema(cls):
+        return _model_one(schema='foo')
+
+    @classmethod
+    def _get_model_schema(cls):
+        return _model_two(schema='foo')
+
+    def test_diffs(self):
+        """test generation of diff rules"""
+
+        metadata = self.m2
+        connection = self.context.bind
+        diffs = []
+        autogenerate._produce_net_changes(connection, metadata, diffs,
+                                          self.autogen_context)
+
+        eq_(
+            diffs[0],
+            ('add_table', metadata.tables['foo.item'])
+        )
+
+        eq_(diffs[1][0], 'remove_table')
+        eq_(diffs[1][1].name, "extra")
+
+        eq_(diffs[2][0], "add_column")
+        eq_(diffs[2][1], "foo")
+        eq_(diffs[2][2], "address")
+        eq_(diffs[2][3], metadata.tables['foo.address'].c.street)
+
+        eq_(diffs[3][0], "add_column")
+        eq_(diffs[3][1], "foo")
+        eq_(diffs[3][2], "order")
+        eq_(diffs[3][3], metadata.tables['foo.order'].c.user_id)
+
+        eq_(diffs[4][0][0], "modify_type")
+        eq_(diffs[4][0][1], "foo")
+        eq_(diffs[4][0][2], "order")
+        eq_(diffs[4][0][3], "amount")
+        eq_(repr(diffs[4][0][5]), "NUMERIC(precision=8, scale=2)")
+        eq_(repr(diffs[4][0][6]), "Numeric(precision=10, scale=2)")
+
+
+        eq_(diffs[5][0], 'remove_column')
+        eq_(diffs[5][3].name, 'pw')
+
+        eq_(diffs[6][0][0], "modify_default")
+        eq_(diffs[6][0][1], "foo")
+        eq_(diffs[6][0][2], "user")
+        eq_(diffs[6][0][3], "a1")
+        eq_(diffs[6][0][6].arg, "x")
+
+        eq_(diffs[7][0][0], 'modify_nullable')
+        eq_(diffs[7][0][5], True)
+        eq_(diffs[7][0][6], False)
+
+    def test_render_nothing(self):
+        context = MigrationContext.configure(
+            connection = self.bind.connect(),
+            opts = {
+                'compare_type' : True,
+                'compare_server_default' : True,
+                'target_metadata' : self.m1,
+                'upgrade_token':"upgrades",
+                'downgrade_token':"downgrades",
+                'alembic_module_prefix': 'op.',
+                'sqlalchemy_module_prefix': 'sa.',
+            }
+        )
+        template_args = {}
+        autogenerate._produce_migration_diffs(context, template_args, set())
+        eq_(re.sub(r"u'", "'", template_args['upgrades']),
+"""### commands auto generated by Alembic - please adjust! ###
+    pass
+    ### end Alembic commands ###""")
+        eq_(re.sub(r"u'", "'", template_args['downgrades']),
+"""### commands auto generated by Alembic - please adjust! ###
+    pass
+    ### end Alembic commands ###""")
+
+    def test_render_diffs(self):
+        """test a full render including indentation"""
+
+        template_args = {}
+        autogenerate._produce_migration_diffs(self.context, template_args, set())
+        eq_(re.sub(r"u'", "'", template_args['upgrades']),
+"""### commands auto generated by Alembic - please adjust! ###
+    op.create_table('item',
+    sa.Column('id', sa.Integer(), nullable=False),
+    sa.Column('description', sa.String(length=100), nullable=True),
+    sa.Column('order_id', sa.Integer(), nullable=True),
+    sa.CheckConstraint('len(description) > 5'),
+    sa.ForeignKeyConstraint(['order_id'], ['foo.order.order_id'], ),
+    sa.PrimaryKeyConstraint('id'),
+    schema='foo'
+    )
+    op.drop_table('extra', schema='foo')
+    op.add_column('address', sa.Column('street', sa.String(length=50), nullable=True), schema='foo')
+    op.add_column('order', sa.Column('user_id', sa.Integer(), nullable=True), schema='foo')
+    op.alter_column('order', 'amount',
+               existing_type=sa.NUMERIC(precision=8, scale=2),
+               type_=sa.Numeric(precision=10, scale=2),
+               nullable=True,
+               existing_server_default='0::numeric',
+               schema='foo')
+    op.drop_column('user', 'pw', schema='foo')
+    op.alter_column('user', 'a1',
+               existing_type=sa.TEXT(),
+               server_default='x',
+               existing_nullable=True,
+               schema='foo')
+    op.alter_column('user', 'name',
+               existing_type=sa.VARCHAR(length=50),
+               nullable=False,
+               schema='foo')
+    ### end Alembic commands ###""")
+        eq_(re.sub(r"u'", "'", template_args['downgrades']),
+"""### commands auto generated by Alembic - please adjust! ###
+    op.alter_column('user', 'name',
+               existing_type=sa.VARCHAR(length=50),
+               nullable=True,
+               schema='foo')
+    op.alter_column('user', 'a1',
+               existing_type=sa.TEXT(),
+               server_default=None,
+               existing_nullable=True,
+               schema='foo')
+    op.add_column('user', sa.Column('pw', sa.VARCHAR(length=50), nullable=True), schema='foo')
+    op.alter_column('order', 'amount',
+               existing_type=sa.Numeric(precision=10, scale=2),
+               type_=sa.NUMERIC(precision=8, scale=2),
+               nullable=False,
+               existing_server_default='0::numeric',
+               schema='foo')
+    op.drop_column('order', 'user_id', schema='foo')
+    op.drop_column('address', 'street', schema='foo')
+    op.create_table('extra',
+    sa.Column('x', sa.CHAR(length=1), nullable=True),
+    sa.Column('uid', sa.INTEGER(), nullable=True),
+    sa.ForeignKeyConstraint(['uid'], ['foo.user.id'], ),
+    sa.PrimaryKeyConstraint(),
+    schema='foo'
+    )
+    op.drop_table('item', schema='foo')
+    ### end Alembic commands ###""")
+
+
 class AutogenerateDiffTest(AutogenTest, TestCase):
     @classmethod
     def _get_db_schema(cls):
         eq_(diffs[1][1].name, "extra")
 
         eq_(diffs[2][0], "add_column")
-        eq_(diffs[2][1], "address")
-        eq_(diffs[2][2], metadata.tables['address'].c.street)
+        eq_(diffs[2][1], None)
+        eq_(diffs[2][2], "address")
+        eq_(diffs[2][3], metadata.tables['address'].c.street)
 
         eq_(diffs[3][0], "add_column")
-        eq_(diffs[3][1], "order")
-        eq_(diffs[3][2], metadata.tables['order'].c.user_id)
+        eq_(diffs[3][1], None)
+        eq_(diffs[3][2], "order")
+        eq_(diffs[3][3], metadata.tables['order'].c.user_id)
 
         eq_(diffs[4][0][0], "modify_type")
-        eq_(diffs[4][0][1], "order")
-        eq_(diffs[4][0][2], "amount")
-        eq_(repr(diffs[4][0][4]), "NUMERIC(precision=8, scale=2)")
-        eq_(repr(diffs[4][0][5]), "Numeric(precision=10, scale=2)")
+        eq_(diffs[4][0][1], None)
+        eq_(diffs[4][0][2], "order")
+        eq_(diffs[4][0][3], "amount")
+        eq_(repr(diffs[4][0][5]), "NUMERIC(precision=8, scale=2)")
+        eq_(repr(diffs[4][0][6]), "Numeric(precision=10, scale=2)")
 
 
         eq_(diffs[5][0], 'remove_column')
-        eq_(diffs[5][2].name, 'pw')
+        eq_(diffs[5][3].name, 'pw')
 
         eq_(diffs[6][0][0], "modify_default")
-        eq_(diffs[6][0][1], "user")
-        eq_(diffs[6][0][2], "a1")
-        eq_(diffs[6][0][5].arg, "x")
+        eq_(diffs[6][0][1], None)
+        eq_(diffs[6][0][2], "user")
+        eq_(diffs[6][0][3], "a1")
+        eq_(diffs[6][0][6].arg, "x")
 
         eq_(diffs[7][0][0], 'modify_nullable')
-        eq_(diffs[7][0][4], True)
-        eq_(diffs[7][0][5], False)
+        eq_(diffs[7][0][5], True)
+        eq_(diffs[7][0][6], False)
 
     def test_render_nothing(self):
         context = MigrationContext.configure(
 
     def test_skip_null_type_comparison_reflected(self):
         diff = []
-        autogenerate._compare_type("sometable", "somecol",
+        autogenerate._compare_type(None, "sometable", "somecol",
             {"name":"somecol", "type":NULLTYPE,
             "nullable":True, "default":None},
             Column("somecol", Integer()),
 
     def test_skip_null_type_comparison_local(self):
         diff = []
-        autogenerate._compare_type("sometable", "somecol",
+        autogenerate._compare_type(None, "sometable", "somecol",
             {"name":"somecol", "type":Integer(),
             "nullable":True, "default":None},
             Column("somecol", NULLTYPE),
                     return dialect.type_descriptor(CHAR(32))
 
         diff = []
-        autogenerate._compare_type("sometable", "somecol",
+        autogenerate._compare_type(None, "sometable", "somecol",
             {"name":"somecol", "type":Integer(),
             "nullable":True, "default":None},
             Column("somecol", MyType()),
         from sqlalchemy.util import OrderedSet
         inspector = Inspector.from_engine(self.bind)
         autogenerate._compare_tables(
-            OrderedSet(['extra', 'user']), OrderedSet(), inspector,
+            OrderedSet([(None, 'extra'), (None, 'user')]), OrderedSet(), inspector,
                 MetaData(), diffs, self.autogen_context
         )
         eq_(
             ")"
         )
 
+    def test_render_table_w_schema(self):
+        m = MetaData()
+        t = Table('test', m,
+            Column('id', Integer, primary_key=True),
+            schema='foo'
+        )
+        eq_ignore_whitespace(
+            autogenerate._add_table(t, self.autogen_context),
+            "op.create_table('test',"
+            "sa.Column('id', sa.Integer(), nullable=False),"
+            "sa.PrimaryKeyConstraint('id'),"
+            "schema='foo'"
+            ")"
+        )
+
     def test_render_drop_table(self):
         eq_(
             autogenerate._drop_table(Table("sometable", MetaData()),
             "op.drop_table('sometable')"
         )
 
+    def test_render_drop_table_w_schema(self):
+        eq_(
+            autogenerate._drop_table(
+                Table("sometable", MetaData(), schema='foo'),
+                self.autogen_context),
+            "op.drop_table('sometable', schema='foo')"
+        )
+
     def test_render_add_column(self):
         eq_(
             autogenerate._add_column(
-                    "foo", Column("x", Integer, server_default="5"),
+                    None, "foo", Column("x", Integer, server_default="5"),
                         self.autogen_context),
             "op.add_column('foo', sa.Column('x', sa.Integer(), "
                 "server_default='5', nullable=True))"
         )
 
+    def test_render_add_column_w_schema(self):
+        eq_(
+            autogenerate._add_column(
+                    "foo", "bar", Column("x", Integer, server_default="5"),
+                        self.autogen_context),
+            "op.add_column('bar', sa.Column('x', sa.Integer(), "
+                "server_default='5', nullable=True), schema='foo')"
+        )
+
     def test_render_drop_column(self):
         eq_(
             autogenerate._drop_column(
-                    "foo", Column("x", Integer, server_default="5"),
+                    None, "foo", Column("x", Integer, server_default="5"),
                         self.autogen_context),
 
             "op.drop_column('foo', 'x')"
         )
 
+    def test_render_drop_column_w_schema(self):
+        eq_(
+            autogenerate._drop_column(
+                    "foo", "bar", Column("x", Integer, server_default="5"),
+                        self.autogen_context),
+
+            "op.drop_column('bar', 'x', schema='foo')"
+        )
+
     def test_render_quoted_server_default(self):
         eq_(
             autogenerate._render_server_default(
                 "existing_type=sa.CHAR(length=20), type_=sa.CHAR(length=10))"
         )
 
+    def test_render_modify_type_w_schema(self):
+        eq_ignore_whitespace(
+            autogenerate._modify_col(
+                        "sometable", "somecolumn",
+                        self.autogen_context,
+                        type_=CHAR(10), existing_type=CHAR(20),
+                        schema='foo'),
+            "op.alter_column('sometable', 'somecolumn', "
+                "existing_type=sa.CHAR(length=20), type_=sa.CHAR(length=10), "
+                "schema='foo')"
+        )
+
     def test_render_modify_nullable(self):
         eq_ignore_whitespace(
             autogenerate._modify_col(
             "existing_type=sa.Integer(), nullable=True)"
         )
 
+    def test_render_modify_nullable_w_schema(self):
+        eq_ignore_whitespace(
+            autogenerate._modify_col(
+                        "sometable", "somecolumn",
+                        self.autogen_context,
+                        existing_type=Integer(),
+                        nullable=True, schema='foo'),
+            "op.alter_column('sometable', 'somecolumn', "
+            "existing_type=sa.Integer(), nullable=True, schema='foo')"
+        )
+
     def test_render_check_constraint_literal(self):
         eq_ignore_whitespace(
             autogenerate._render_check_constraint(
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.