Repository: ariga/atlas
Pull request #2072 (2023-09-12 11:52:55)

doc: doc for new 'atlas migrate lint' behavior


Patch:
diff --git a/doc/md/cloud/setup-ci.mdx b/doc/md/cloud/setup-ci.mdx
index d9e8f905..5fb9375e 100644
--- a/doc/md/cloud/setup-ci.mdx
+++ b/doc/md/cloud/setup-ci.mdx
@@ -13,23 +13,22 @@ schema is automatically reviewed and tested before it is deployed to production.
 
 In this guide, we'll show you how to set up CI for your database using Atlas Cloud.
 
-## Push or Pull Workflows
+## GitHub Actions or App Workflows
 
 There are two ways to set up CI for your database with Atlas Cloud:
 
-* **Pull (GitHub App)** - You install the Atlas Cloud GitHub App on your GitHub
-  repository. Atlas Cloud will automatically pull changes from your GitHub
-  repository, then run tests on them.
+* **GitHub Action** (recommended) - Install the [`ariga/atlas-action`](https://github.com/ariga/atlas-action)
+  GitHub Action on your GitHub repository and configure it to run on any pull request that modifies your database schema.
 
-  This is the fastest way to set Atlas Cloud up, but it does require that you
-  have the necessary permissions to install the GitHub App on your repository.
+  Note, this workflow does _not_ require you to grant Atlas Cloud any permissions on your repository. It also gives you
+  more control on how to set it up, and what [Dev Database](/concepts/dev-database) to use (e.g, PostGIS) for linting, change simulation, etc.
 
-* **Push (GitHub Action)** - You install the [`ariga/atlas-action`](https://github.com/ariga/atlas-action)
-  GitHub Action on your GitHub repository and configure it to run on any pull request
-  that modifies your database schema.
+* **GitHub App** - Install the Atlas Cloud GitHub App on your GitHub repository. Atlas Cloud will automatically pull
+  changes from your GitHub repository, then run tests on them.
+
+  This workflow is faster to set up, but it does require the necessary permissions to install the GitHub
+  App on your repository.
 
-  This is a bit more work to set up, but it does _not_ require you to grant Atlas
-  Cloud any permissions on your repository.
 
 ## Prerequisites
 
@@ -37,126 +36,16 @@ There are two ways to set up CI for your database with Atlas Cloud:
    and would like to experiment with this workflow, you can use the
    [`ariga/atlas-template`](https://github.com/ariga/atlas-template) template repo
    to create one for you.
-2. An [Atlas Cloud](https://atlasgo.cloud) account. If you don't have one, you can
-   [sign up for free](https://auth.atlasgo.cloud/signup).
-
-## Pull (GitHub App) Workflow
-
-### Step 1: Install the Atlas Cloud GitHub App
-
-After logging in to Atlas Cloud, create a new migration directory by clicking on
-"Migrations Directories" in the sidebar and then clicking on the "Add Directory"
-button.
-
-<details><summary>Screenshot Example</summary>
-
-![](https://atlasgo.io/uploads/cloud/ci-guide/create-dir-1.png)
-
-</details>
-
-Click "Connect with GitHub" to initiate the GitHub App installation flow.
-
-<details>
-<summary>Screenshot Example</summary>
-
-![](https://atlasgo.io/uploads/cloud/ci-guide/create-dir-2.png)
-</details>
-
-### Step 2: Connect Your Repository
-
-Once you've installed the GitHub App, you need to grant it access to
-the relevant repositories. Click on the "Organization" field and choose
-"Add Organization" from the dropdown:
-
-<details>
-<summary>Screenshot Example</summary>
-
-![](https://atlasgo.io/uploads/cloud/ci-guide/create-dir-3.png)
-</details>
-
-A popup requesting you to grant permission to specific repositories will appear.
-Select the repositories you want to grant access to, then click "Install and Authorize".
-
-<details>
-<summary>Screenshot Example</summary>
-
-![](https://atlasgo.io/uploads/cloud/ci-guide/create-dir-4.png)
-</details>
-
-### Step 3: Configure the Migration Directory
-
-Fill the rest of the form with the relevant configuration details:
-* **Directory Path** - The path to the directory containing your migrations relative to your repository root.
-* **Directory Name** - An identifier for the directory. This will be used to identify the directory in the Atlas Cloud and APIs.
-* **Migration Engine** - The format of the migrations in the directory. Atlas works best with migration directories in its
-  format, but also supports `golang-migrate`, Flyway, and Liquibase.
-* **Database** - the database engine for the target database.
-
-<details>
-<summary>Screenshot Example</summary>
-
-![](https://atlasgo.io/uploads/cloud/ci-guide/create-dir-5.png)
-</details>
-
-Click "Connect Directory" to finish the process.
-
-### Step 4: Test the Integration
-
-After connecting your directory, let's create a pull request with a destructive
-change to make sure that Atlas Cloud catches it.
-
-Create a new branch:
-
-```
-git checkout -b test-branch
-```
-
-Create a new migration file:
-
-```
-atlas migrate new --edit destructive
-```
-
-Add the following SQL to the migration file and save:
-
-```sql
-DROP TABLE posts;
-```
-
-Commit the changes and push them to GitHub:
-
-```
-git add .
-git commit -m "add destructive migration"
-git push origin test-branch
-```
-
-Create a pull request from the `test-branch` branch to your main branch.
-
-After a few seconds, Atlas Cloud will pick up the changes and run CI against
-them. Shortly after, you will see a comment on your pull request with the
-results of the CI run:
-
-<details>
-<summary>Screenshot Example</summary>
-
-![](https://atlasgo.io/uploads/cloud/ci-guide/app-ci.png)
-</details>
+2. An [Atlas Cloud](https://atlasgo.cloud) account. If you don't have one, you can sign up for free in
+  [auth.atlasgo.cloud](https://auth.atlasgo.cloud/signup), or by running `atlas login` in your terminal:
 
+  ```shell
+  $ atlas login
+  //highlight-next-line-info
+  You are now connected to "a8m" on Atlas Cloud.
+  ```
 
-Amazing! Atlas Cloud caught the destructive change and prevented it from being
-accidentally merged to your main branch.
-
-To see a more detailed report of the CI run, click on the "Full Report on Atlas Cloud"
-link in the comment:
-
-<details>
-<summary>Screenshot Example</summary>
-
-![](https://atlasgo.io/uploads/cloud/ci-guide/app-cloud-report.png)
-</details>
-
-## Push (GitHub Action) Workflow
+## GitHub Action Workflow
 
 ### Step 1: Create a Bot Token for Atlas Cloud
 
@@ -376,6 +265,123 @@ and sync the migrations to Atlas Cloud on every push to `master`.
   ![](https://atlasgo.io/uploads/cloud/ci-guide/fixed-cloud-report-2.png)
   </details>
 
+
+## GitHub App Workflow
+
+### Step 1: Install the Atlas Cloud GitHub App
+
+After logging in to Atlas Cloud, create a new migration directory by clicking on
+"Projects" in the sidebar and then click on the "Add Directory"
+button.
+
+<details><summary>Screenshot Example</summary>
+
+![](https://atlasgo.io/uploads/cloud/ci-guide/create-dir-1.png)
+
+</details>
+
+Click "Connect with GitHub" to initiate the GitHub App installation flow.
+
+<details>
+<summary>Screenshot Example</summary>
+
+![](https://atlasgo.io/uploads/cloud/ci-guide/create-dir-2.png)
+</details>
+
+### Step 2: Connect Your Repository
+
+Once you've installed the GitHub App, you need to grant it access to
+the relevant repositories. Click on the "Organization" field and choose
+"Add Organization" from the dropdown:
+
+<details>
+<summary>Screenshot Example</summary>
+
+![](https://atlasgo.io/uploads/cloud/ci-guide/create-dir-3.png)
+</details>
+
+A popup requesting you to grant permission to specific repositories will appear.
+Select the repositories you want to grant access to, then click "Install and Authorize".
+
+<details>
+<summary>Screenshot Example</summary>
+
+![](https://atlasgo.io/uploads/cloud/ci-guide/create-dir-4.png)
+</details>
+
+### Step 3: Configure the Migration Directory
+
+Fill the rest of the form with the relevant configuration details:
+* **Directory Path** - The path to the directory containing your migrations relative to your repository root.
+* **Directory Name** - An identifier for the directory. This will be used to identify the directory in the Atlas Cloud and APIs.
+* **Migration Engine** - The format of the migrations in the directory. Atlas works best with migration directories in its
+  format, but also supports `golang-migrate`, Flyway, and Liquibase.
+* **Database** - the database engine for the target database.
+
+<details>
+<summary>Screenshot Example</summary>
+
+![](https://atlasgo.io/uploads/cloud/ci-guide/create-dir-5.png)
+</details>
+
+Click "Connect Directory" to finish the process.
+
+### Step 4: Test the Integration
+
+After connecting your directory, let's create a pull request with a destructive
+change to make sure that Atlas Cloud catches it.
+
+Create a new branch:
+
+```
+git checkout -b test-branch
+```
+
+Create a new migration file:
+
+```
+atlas migrate new --edit destructive
+```
+
+Add the following SQL to the migration file and save:
+
+```sql
+DROP TABLE posts;
+```
+
+Commit the changes and push them to GitHub:
+
+```
+git add .
+git commit -m "add destructive migration"
+git push origin test-branch
+```
+
+Create a pull request from the `test-branch` branch to your main branch.
+
+After a few seconds, Atlas Cloud will pick up the changes and run CI against
+them. Shortly after, you will see a comment on your pull request with the
+results of the CI run:
+
+<details>
+<summary>Screenshot Example</summary>
+
+![](https://atlasgo.io/uploads/cloud/ci-guide/app-ci.png)
+</details>
+
+
+Amazing! Atlas Cloud caught the destructive change and prevented it from being
+accidentally merged to your main branch.
+
+To see a more detailed report of the CI run, click on the "Full Report on Atlas Cloud"
+link in the comment:
+
+<details>
+<summary>Screenshot Example</summary>
+
+![](https://atlasgo.io/uploads/cloud/ci-guide/app-cloud-report.png)
+</details>
+
 ## Summary
 
 In this guide, we've shown how to configure Atlas Cloud to apply continuous integration
diff --git a/doc/md/guides/ci-platforms/gitlab.mdx b/doc/md/guides/ci-platforms/gitlab.mdx
index f6621736..5c078de3 100644
--- a/doc/md/guides/ci-platforms/gitlab.mdx
+++ b/doc/md/guides/ci-platforms/gitlab.mdx
@@ -12,7 +12,7 @@ GitLab is a popular, open-source alternative to GitHub.  In addition to a self-h
 GitLab also offers a hosted version at [gitlab.com](https://gitlab.com). Similar to GitHub, GitLab offers
 users storage for Git repositories, issue tracking, and CI/CD pipelines.
 
-In this guide we will demonstrate how to use [GitLab CI](https://docs.gitlab.com/ee/ci/)) and Atlas to setup CI
+In this guide we will demonstrate how to use [GitLab CI](https://docs.gitlab.com/ee/ci/) and Atlas to setup CI
 pipelines for your database schema changes.
 
 ## Prerequisites
diff --git a/doc/md/lint/analyzers.mdx b/doc/md/lint/analyzers.mdx
index 2191c956..0f209f0f 100644
--- a/doc/md/lint/analyzers.mdx
+++ b/doc/md/lint/analyzers.mdx
@@ -24,6 +24,27 @@ safety of migration scripts.
 
 Below are the `Analyzer` implementations currently supported by Atlas.
 
+### Non-Linear Changes
+
+Non-additive changes, often referred to as non-linear changes, are changes to the migration directory that are not added
+in a sequential order. This is a bit like the linear history in version control systems, where migration files are
+commits and the migration directory is the repository. Let's explain with three examples why ensuring the linearity of the
+migration directory is important:
+
+- When a developer introduces a new migration file without having the **latest state** of the directory, there is a risk
+  of generating an incorrect file that might conflict with the actual schema and cause a failure during deployment.
+- When a developer merges a feature branch to the main branch with a new migration file that is not positioned at the
+  end of the directory, there is a risk that Atlas will skip this migration file, as there might be some database that already
+  contains a higher version of the migration directory. This scenario can cause unexpected and surprising behavior during
+  deployments.
+- Having non-linear history can make it challenging to roll back (or revert changes) to a specific version of the migration
+  directory, as the state of the database might be different than the state of the migration directory. Applying the migration
+  directory in consistent order promises deterministic behavior.
+
+Luckily, Atlas detects non-linear and non-additive changes made to a migration directory.
+To enable this behavior in your project, integrate Atlas into your [GitHub Actions](/cloud/setup-ci) or [GitLab](/guides/ci-platforms/gitlab)
+CI pipelines, and Atlas will automatically detect and report non-linear changes during the CI run.
+
 ### Destructive Changes
 
 Destructive changes are changes to a database schema that result in loss of data. For instance,
diff --git a/doc/md/versioned/diff.mdx b/doc/md/versioned/diff.mdx
index 0eca6272..3473f26f 100644
--- a/doc/md/versioned/diff.mdx
+++ b/doc/md/versioned/diff.mdx
@@ -14,7 +14,125 @@ where each change is explicitly defined as a migration script with a specific ve
 
 Practically speaking, this means developers define the desired state and Atlas maintains the `migrations` directory,
 which contains the explicit SQL scripts to move from one version to the next. The desired state can be defined using an
-[HCL](/atlas-schema/hcl) or SQL schema definition, or a database [URL](../concepts/url).
+[HCL](/atlas-schema/hcl) or SQL schema definition, a database [URL](../concepts/url), external schemas like
+[ORM](/atlas-schema/external).
+
+### Summary
+
+To get started with versioned migrations, run `atlas migrate diff`. This command generates a new migration file that aligns
+the migration directory with the state defined by the desired schema. Below are a few examples of how to use this command
+to generate migrations from various sources:
+
+<Tabs>
+<TabItem label="SQL Schema" value="sql">
+
+**MySQL/MariaDB:**
+
+```shell {3}
+atlas migrate diff create_users \
+  --dir "file://migrations" \
+  --to "file://schema.sql" \
+  --dev-url "docker://mysql/8/dev" # Or: "docker://mariadb/latest/dev"
+```
+
+**PostgreSQL:**
+
+```shell {3}
+atlas migrate diff create_users \
+  --dir "file://migrations" \
+  --to "file://schema.sql" \
+  --dev-url "docker://postgres/15/dev?search_path=public"
+```
+</TabItem>
+<TabItem label="HCL Schema" value="hcl">
+
+**MySQL/MariaDB:**
+
+```shell {3}
+atlas migrate diff create_users \
+  --dir "file://migrations" \
+  --to "file://schema.hcl" \
+  --dev-url "docker://mysql/8/dev" # Or: "docker://mariadb/latest/dev"
+```
+
+**PostgreSQL:**
+
+```shell {3}
+atlas migrate diff create_users \
+  --dir "file://migrations" \
+  --to "file://schema.hcl" \
+  --dev-url "docker://postgres/15/dev?search_path=public"
+```
+</TabItem>
+<TabItem label="ORM" value="orm">
+
+**MySQL/MariaDB:**
+
+```shell {2}
+atlas migrate diff create_users \
+  --env "gorm"
+  --dev-url "docker://mysql/8/dev" # Or: "docker://mariadb/latest/dev"
+```
+
+**PostgreSQL:**
+
+```shell {2}
+atlas migrate diff create_users \
+  --env "gorm"
+  --dev-url "docker://postgres/15/dev?search_path=public"
+```
+
+To learn more on how to configure Atlas to read the desired state of a schema from an ORM definition, see the
+[external schema documentation](/atlas-schema/external).
+
+</TabItem>
+<TabItem label="Database Schema" value="database-schema">
+
+**MySQL/MariaDB:**
+
+```shell {3}
+atlas migrate diff create_users \
+  --dir "file://migrations" \
+  --to "mysql://root:pass@:3306/public" \
+  --dev-url "docker://mysql/8/dev" # Or: "docker://mariadb/latest/dev"
+```
+
+**PostgreSQL:**
+
+```shell {3}
+atlas migrate diff create_users \
+  --dir "file://migrations" \
+  --to "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable" \
+  --dev-url "docker://postgres/15/dev?search_path=public"
+```
+</TabItem>
+<TabItem label="Database (multiple schemas)" value="database">
+
+**MySQL/MariaDB:**
+
+```shell {3}
+atlas migrate diff create_users \
+  --dir "file://migrations" \
+  --to "mysql://root:pass@:3306/" \
+  --dev-url "docker://mysql/8" # Or: "docker://mariadb"
+```
+
+**PostgreSQL:**
+
+```shell {3}
+atlas migrate diff create_users \
+  --dir "file://migrations" \
+  --to "postgres://postgres:pass@localhost:5432/database?sslmode=disable" \
+  --dev-url "docker://postgres/15/dev"
+```
+</TabItem>
+</Tabs>
+
+:::info [Ensure linear migration history](/lint/analyzers#non-linear-changes)
+By utilizing the [`atlas migrate lint`](/versioned/lint) command, teams can ensure that changes are added to the
+migration directory in a sequential order, preventing unexpected behavior. Learn more on how to integrate Atlas into your
+[**GitHub Actions**](/cloud/setup-ci) or [**GitLab**](/guides/ci-platforms/gitlab) CI pipelines.
+:::
 
 ### Generate migrations from HCL schemas
 
@@ -40,42 +158,19 @@ creating our `users` table:
 * `--format` (optional) - [Go template](https://pkg.go.dev/text/template) to use to format the output.
 
 <Tabs
-defaultValue="docker"
 values={[
-{label: 'Docker', value: 'docker'},
 {label: 'MySQL', value: 'mysql'},
 {label: 'MariaDB', value: 'maria'},
 {label: 'PostgreSQL', value: 'postgres'},
 {label: 'SQLite', value: 'sqlite'},
 ]}>
-<TabItem value="docker">
-
-**MySQL/MariaDB:**
-
-```shell
-atlas migrate diff create_users \
-  --dir "file://migrations" \
-  --to "file://schema.hcl" \
-  --dev-url "docker://mysql/8/test" # Or: "docker://mariadb/latest/test"
-```
-
-**PostgreSQL:**
-
-```shell
-atlas migrate diff create_users \
-  --dir "file://migrations" \
-  --to "file://schema.hcl" \
-  --dev-url "docker://postgres/15/test?search_path=public"
-```
-
-</TabItem>
 <TabItem value="mysql">
 
 ```shell
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "mysql://root:pass@:3306/test"
+  --dev-url "docker://mysql/8/test"
 ```
 
 </TabItem>
@@ -85,7 +180,7 @@ atlas migrate diff create_users \
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "maria://root:pass@:3306/test"
+  --dev-url "docker://mariadb/latest/test"
 ```
 
 </TabItem>
@@ -95,7 +190,7 @@ atlas migrate diff create_users \
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
+  --dev-url "docker://postgres/15/test?search_path=public"
 ```
 
 </TabItem>
@@ -170,42 +265,19 @@ table "users" {
 Then, run `atlas migrate diff`:
 
 <Tabs
-defaultValue="docker"
 values={[
-{label: 'Docker', value: 'docker'},
 {label: 'MySQL', value: 'mysql'},
 {label: 'MariaDB', value: 'maria'},
 {label: 'PostgreSQL', value: 'postgres'},
 {label: 'SQLite', value: 'sqlite'},
 ]}>
-<TabItem value="docker">
-
-**MySQL/MariaDB:**
-
-```shell
-atlas migrate diff create_users \
-  --dir "file://migrations" \
-  --to "file://schema.hcl" \
-  --dev-url "docker://mysql/8/test" # Or: "docker://mariadb/latest/test"
-```
-
-**PostgreSQL:**
-
-```shell
-atlas migrate diff create_users \
-  --dir "file://migrations" \
-  --to "file://schema.hcl" \
-  --dev-url "docker://postgres/15/test?search_path=public"
-```
-
-</TabItem>
 <TabItem value="mysql">
 
 ```shell
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "mysql://root:pass@:3306/test"
+  --dev-url "docker://mysql/8/dev"
 ```
 
 </TabItem>
@@ -215,7 +287,7 @@ atlas migrate diff create_users \
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "maria://root:pass@:3306/test"
+  --dev-url "docker://mariadb/latest/dev"
 ```
 
 </TabItem>
@@ -225,7 +297,7 @@ atlas migrate diff create_users \
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
+  --dev-url "docker://postgres/15/dev?search_path=public"
 ```
 
 </TabItem>
@@ -235,7 +307,7 @@ atlas migrate diff create_users \
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "sqlite://file?mode=memory"
+  --dev-url "sqlite://dev?mode=memory"
 ```
 
 </TabItem>
@@ -311,42 +383,19 @@ creating our `users` table:
 * `--dev-url` a [URL](/concepts/url) to a [Dev Database](/concepts/dev-database) that will be used to compute the diff.
 
 <Tabs
-defaultValue="docker"
 values={[
-{label: 'Docker', value: 'docker'},
 {label: 'MySQL', value: 'mysql'},
 {label: 'MariaDB', value: 'maria'},
 {label: 'PostgreSQL', value: 'postgres'},
 {label: 'SQLite', value: 'sqlite'},
 ]}>
-<TabItem value="docker">
-
-**MySQL/MariaDB:**
-
-```shell
-atlas migrate diff create_users \
-  --dir "file://migrations" \
-  --to "file://schema.sql" \
-  --dev-url "docker://mysql/8/test" # Or: "docker://mariadb/latest/test"
-```
-
-**PostgreSQL:**
-
-```shell
-atlas migrate diff create_users \
-  --dir "file://migrations" \
-  --to "file://schema.sql" \
-  --dev-url "docker://postgres/15/test?search_path=public"
-```
-
-</TabItem>
 <TabItem value="mysql">
 
 ```shell
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.sql" \
-  --dev-url "mysql://root:pass@:3306/test"
+  --dev-url "docker://mysql/8/dev"
 ```
 
 </TabItem>
@@ -356,7 +405,7 @@ atlas migrate diff create_users \
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.sql" \
-  --dev-url "maria://root:pass@:3306/test"
+  --dev-url "docker://mariadb/latest/dev"
 ```
 
 </TabItem>
@@ -366,7 +415,7 @@ atlas migrate diff create_users \
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.sql" \
-  --dev-url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
+  --dev-url "docker://postgres/15/dev?search_path=public"
 ```
 
 </TabItem>
@@ -435,42 +484,19 @@ Then, run `atlas migrate diff`:
 
 
 <Tabs
-defaultValue="docker"
 values={[
-{label: 'Docker', value: 'docker'},
 {label: 'MySQL', value: 'mysql'},
 {label: 'MariaDB', value: 'maria'},
 {label: 'PostgreSQL', value: 'postgres'},
 {label: 'SQLite', value: 'sqlite'},
 ]}>
-<TabItem value="docker">
-
-**MySQL/MariaDB:**
-
-```shell
-atlas migrate diff create_users \
-  --dir "file://migrations" \
-  --to "file://schema.sql" \
-  --dev-url "docker://mysql/8/test" # Or: "docker://mariadb/latest/test"
-```
-
-**PostgreSQL:**
-
-```shell
-atlas migrate diff create_users \
-  --dir "file://migrations" \
-  --to "file://schema.sql" \
-  --dev-url "docker://postgres/15/test?search_path=public"
-```
-
-</TabItem>
 <TabItem value="mysql">
 
 ```shell
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.sql" \
-  --dev-url "mysql://root:pass@:3306/test"
+  --dev-url "docker://mysql/8/dev"
 ```
 
 </TabItem>
@@ -480,7 +506,7 @@ atlas migrate diff create_users \
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.sql" \
-  --dev-url "maria://root:pass@:3306/test"
+  --dev-url "docker://mariadb/latest/dev"
 ```
 
 </TabItem>
@@ -490,7 +516,7 @@ atlas migrate diff create_users \
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.sql" \
-  --dev-url "postgres://postgres:pass@localhost:5432/database?search_path=public&sslmode=disable"
+  --dev-url "docker://postgres/15/dev?search_path=public"
 ```
 
 </TabItem>
@@ -574,7 +600,7 @@ creating our `users` table:
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "mysql://root:pass@:3306/public" \
-  --dev-url "mysql://root:pass@:3306/test"
+  --dev-url "docker://mysql/8/dev"
 ```
 
 Run `ls migrations`, and you will notice Atlas created 2 files:
@@ -623,7 +649,7 @@ the output:
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "mysql://root:pass@:3306/test" \
+  --dev-url "docker://mysql/8/dev" \
   --qualifier "market"
 ```
 
@@ -644,7 +670,7 @@ directory by passing in the `format` query parameter to the migration directory
 atlas migrate diff create_users \
   --dir "file://migrations?format=golang-migrate" \
   --to "file://schema.hcl" \
-  --dev-url "mysql://root:pass@:3306/test"
+  --dev-url "docker://mysql/8/dev"
 ```
 
 Run `ls migrations`, and you will notice Atlas created 3 files:
@@ -729,7 +755,7 @@ values={[
 atlas migrate diff create_all \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "mysql://root:pass@:3306/"
+  --dev-url "docker://mysql/8"
 ```
 
 Running `cat migrations/*.sql` will print the followings:
@@ -752,7 +778,7 @@ CREATE TABLE `market`.`users` (`name` int NOT NULL) CHARSET utf8mb4 COLLATE utf8
 atlas migrate diff create_all \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "postgres://root:pass@:5434/database?sslmode=disable"
+  --dev-url "docker://postgres/15"
 ```
 
 Running `cat migrations/*.sql` will print the followings:
@@ -836,7 +862,7 @@ generate the SQL statements with indentation, use the `--format` flag. For examp
 atlas migrate diff create_users \
   --dir "file://migrations" \
   --to "file://schema.hcl" \
-  --dev-url "docker://mysql/8/test" \
+  --dev-url "docker://mysql/8/dev" \
   --format '{{ sql . "  " }}'
 ```
 
diff --git a/doc/md/versioned/lint.mdx b/doc/md/versioned/lint.mdx
index 5e962498..f6f7d78e 100644
--- a/doc/md/versioned/lint.mdx
+++ b/doc/md/versioned/lint.mdx
@@ -7,29 +7,179 @@ title: Verifying migration safety
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-With the `atlas migrate lint` command, users can analyze the migration directory
-to detect potentially dangerous changes to the database schema. This command may be
-incorporated in _continuous integration_ pipelines to enable teams to enforce desired
-policies with regard to schema changes. 
+The `atlas migrate lint` command enables team members to work together on database schemas, ensuring they are synchronized
+and avoid stepping on each other's toes. Atlas analyzes new schema changes in the migration directory that might
+be dangerous, violate best practices, or break the team's policies. Learn more about [Atlas's analyzers](/lint/analyzers).
 
-[Learn more about Atlas's analyzers](/lint/analyzers)
+While this command is commonly used locally, migration linting is frequently incorporated into CI pipelines, allowing
+teams to detect changes early before merging them into the main branch. Learn more on how to integrate Atlas into
+your [GitHub Actions](/cloud/setup-ci) or [GitLab](/guides/ci-platforms/gitlab) CI pipelines.
 
 ### Flags
 
 When using `migrate lint` to analyze migrations, users must supply multiple parameters:
-* `--dev-url` a [URL](/concepts/url) to a [Dev-database](/concepts/dev-database) that will be used
+* `--dev-url` - [URL](/concepts/url) to a [Dev-database](/concepts/dev-database) that will be used
  to simulate the changes and verify their correctness.
-* `--dir` the URL of the migration directory, by default it is `file://migrations`, e.g a
+* `--dir` - URL of the migration directory, by default it is `file://migrations`, e.g a
  directory named `migrations` in the current working directory.
+* `--web` / `-w` (optional) - open the lint report in the browser.
 
 ### Changeset detection
 
-When we run the `lint` command, we need to instruct Atlas on how to decide what 
-set of migration files to analyze. Currently, two modes are supported.
+When we run the `lint` command, we need to instruct Atlas on how to decide which set of migration files to analyze.
+Currently, three ways are supported. Let's go over them one by one:
 
-* `--git-base <branchName>`: which selects the diff between the provided branch and 
- the current one as the changeset.
-* `--latest <n>` which selects the latest n migration files as the changeset.
+#### Compare against the latest state of the migration directory
+
+Teams that have connected their project to Atlas Cloud (see [setup](/cloud/setup-ci) for more details), can simply run
+the following command to analyze the changes detected by comparing the **local state** of the migration directory
+to the **latest state**, as defined in Atlas Cloud. For example, either by running `atlas migrate push` or by connecting
+it to a CI pipeline.
+
+<Tabs>
+<TabItem value="mysql" label="MySQL">
+
+```shell
+atlas migrate lint \
+  --dev-url "docker://mysql/8/dev" \
+  -w
+```
+
+</TabItem>
+<TabItem value="maria" label="MariaDB">
+
+```shell
+atlas migrate lint \
+  --dev-url "docker://mariadb/latest/dev" \
+  -w
+```
+
+</TabItem>
+<TabItem value="postgres" label="PostgreSQL">
+
+```shell
+atlas migrate lint \
+  --dev-url "docker://postgres/15/test?search_path=public" \
+  -w
+```
+
+</TabItem>
+<TabItem value="sqlite" label="SQLite">
+
+```shell
+atlas migrate lint \
+  --dev-url "sqlite://file?mode=memory" \
+  -w
+```
+
+</TabItem>
+</Tabs>
+
+:::info Preventing non-additive changes
+By using this method, Atlas warns you if your local migration directory is not up-to-date with the latest state
+of the project and rebasing is required. For example, while working on a feature branch, another team member might have
+pushed a new migration file to the main branch.
+:::
+
+Here is an example of how a report of `atlas migrate lint` looks like:
+
+![CI for schema changes](https://atlasgo.io/uploads/lint-report-non-additive.png)
+
+
+#### Analyze the latest N migration files
+
+Users can instruct Atlas to analyze the latest N migration files by running `atlas migrate lint --latest N`:
+
+<Tabs>
+<TabItem value="mysql" label="MySQL">
+
+```shell
+atlas migrate lint \
+  --dev-url "docker://mysql/8/dev" \
+  --latest 1
+```
+
+</TabItem>
+<TabItem value="maria" label="MariaDB">
+
+```shell
+atlas migrate lint \
+  --dev-url "docker://mariadb/latest/dev" \
+  --latest 1
+```
+
+</TabItem>
+<TabItem value="postgres" label="PostgreSQL">
+
+```shell
+atlas migrate lint \
+  --dev-url "docker://postgres/15/test?search_path=public" \
+  --latest 1
+```
+
+</TabItem>
+<TabItem value="sqlite" label="SQLite">
+
+```shell
+atlas migrate lint \
+  --dev-url "sqlite://file?mode=memory" \
+  --latest 1
+```
+
+</TabItem>
+</Tabs>
+
+Similarly to the previous method, the `-w`/`--web` can be used to open the report in the browser, see the changes in ERD
+format, and more.
+
+#### Compare against a specific branch
+
+Users can instruct Atlas to detect which changes to analyze by comparing the current branch to a specific branch.
+For example, in order to analyze the changes between the current branch and the `main` branch, and open the report in the
+browser, run the following command:
+
+<Tabs>
+<TabItem value="mysql" label="MySQL">
+
+```shell
+atlas migrate lint \
+  --dev-url "docker://mysql/8/dev" \
+  --git-base "master" \
+  -w
+```
+
+</TabItem>
+<TabItem value="maria" label="MariaDB">
+
+```shell
+atlas migrate lint \
+  --dev-url "docker://mariadb/latest/dev" \
+  --git-base "master" \
+  -w
+```
+
+</TabItem>
+<TabItem value="postgres" label="PostgreSQL">
+
+```shell
+atlas migrate lint \
+  --dev-url "docker://postgres/15/test?search_path=public" \
+  --git-base "master" \
+  -w
+```
+
+</TabItem>
+<TabItem value="sqlite" label="SQLite">
+
+```shell
+atlas migrate lint \
+  --dev-url "sqlite://file?mode=memory" \
+  --git-base "master" \
+  -w
+```
+
+</TabItem>
+</Tabs>
 
 ### `nolint` directive
 
@@ -141,79 +291,81 @@ lines are used to separate file directives from its content.
 </TabItem>
 </Tabs>
 
-### Output
-
-Users may supply a [Go template](https://pkg.go.dev/text/template) string as the `--format` parameter to
-format the output of the `lint` command.
+### Open in the browser
 
-### Examples
-
-Analyze the latest migration file using the `docker` driver:
+In order to open the migration linting report in the browser, you first need to be logged in to Atlas, then use one of
+the example commands above with the `-w`/`--web` flag. For example:
 
 <Tabs>
-<TabItem value="MySQL">
+<TabItem value="mysql" label="MySQL">
 
 ```shell
-atlas migrate lint \
-  --latest 1 \
-  --dir "file://migrations" \
-  --dev-url "docker://mysql/8/test"
+atlas login
 ```
 
-</TabItem>
-<TabItem value="MariaDB">
-
 ```shell
 atlas migrate lint \
+  --dev-url "docker://mysql/8/dev" \
   --latest 1 \
-  --dir "file://migrations" \
-  --dev-url "docker://mariadb/latest/test"
+  -w
 ```
 
 </TabItem>
-<TabItem value="PostgreSQL">
+<TabItem value="maria" label="MariaDB">
+
+```shell
+atlas login
+```
 
 ```shell
 atlas migrate lint \
+  --dev-url "docker://mariadb/latest/dev" \
   --latest 1 \
-  --dir "file://migrations" \
-  --dev-url "docker://postgres/15/test?search_path=public"
+  -w
 ```
 
 </TabItem>
-<TabItem value="SQLite">
+<TabItem value="postgres" label="PostgreSQL">
+
+```shell
+atlas login
+```
 
 ```shell
 atlas migrate lint \
+  --dev-url "docker://postgres/15/test?search_path=public" \
   --latest 1 \
-  --dir "file://migrations" \
-  --dev-url "sqlite://file?mode=memory"
+  -w
 ```
 
 </TabItem>
-</Tabs>
+<TabItem value="sqlite" label="SQLite">
 
-Analyze all changes relative to the `master` Git branch:
 ```shell
-atlas migrate lint \
-  --dir "file://my/project/migrations" \
-  --dev-url "mysql://root:pass@localhost:3306/dev" \
-  --git-base "master"
+atlas login
 ```
 
-Format output as JSON:
 ```shell
 atlas migrate lint \
-  --dir "file://my/project/migrations" \
-  --dev-url "mysql://root:pass@localhost:3306/dev" \
-  --git-base "master" \
-  --format "{{ json .Files }}"
-```
-Use the configuration defined in [`atlas.hcl`](../atlas-schema/projects#configure-migration-linting):
-```shell
-atlas migrate lint --env "local"
+  --dev-url "sqlite://file?mode=memory" \
+  --latest 1 \
+  -w
 ```
 
+</TabItem>
+</Tabs>
+
+<details><summary>Screenshot example</summary>
+
+![CI for schema changes](https://atlasgo.io/uploads/lint-report-non-additive.png)
+
+</details>
+
+### Output
+
+Users may supply a [Go template](https://pkg.go.dev/text/template) string as the `--format` parameter to
+format the output of the `lint` command.
+
 ### Reference
 
 [CLI Command Reference](/cli-reference#atlas-migrate-lint)
\ No newline at end of file
diff --git a/schemahcl/stdlib.go b/schemahcl/stdlib.go
index 8c60a130..971205be 100644
--- a/schemahcl/stdlib.go
+++ b/schemahcl/stdlib.go
@@ -161,7 +161,8 @@ func stdFuncs() map[string]function.Function {
 		"trimsuffix":      stdlib.TrimSuffixFunc,
 		"try":             tryfunc.TryFunc,
 		"upper":           stdlib.UpperFunc,
-		"urlescape":       urlEscape,
+		"urlescape":       urlEscapeFunc,
+		"urluserinfo":     urlUserinfoFunc,
 		"urlqueryset":     urlQuerySetFunc,
 		"urlsetpath":      urlSetPathFunc,
 		"values":          stdlib.ValuesFunc,
@@ -276,6 +277,37 @@ var (
 			return cty.StringVal(u.String()), nil
 		},
 	})
+	urlUserinfoFunc = function.New(&function.Spec{
+		Params: []function.Parameter{
+			{
+				Name: "url",
+				Type: cty.String,
+			},
+			{
+				Name: "user",
+				Type: cty.String,
+			},
+		},
+		VarParam: &function.Parameter{
+			Name:      "pass",
+			Type:      cty.String,
+			AllowNull: true,
+		},
+		Type: function.StaticReturnType(cty.String),
+		Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
+			u, err := url.Parse(args[0].AsString())
+			if err != nil {
+				return cty.NilVal, err
+			}
+			user := args[1].AsString()
+			if len(args) > 2 && !args[2].IsNull() {
+				u.User = url.UserPassword(user, args[2].AsString())
+			} else {
+				u.User = url.User(user)
+			}
+			return cty.StringVal(u.String()), nil
+		},
+	})
 	urlSetPathFunc = function.New(&function.Spec{
 		Params: []function.Parameter{
 			{
@@ -298,7 +330,7 @@ var (
 		},
 	})
 
-	urlEscape = function.New(&function.Spec{
+	urlEscapeFunc = function.New(&function.Spec{
 		Params: []function.Parameter{
 			{
 				Name: "string",


Test patch:
diff --git a/schemahcl/stdlib_test.go b/schemahcl/stdlib_test.go
index c7933d4a..cdb9fc58 100644
--- a/schemahcl/stdlib_test.go
+++ b/schemahcl/stdlib_test.go
@@ -111,7 +111,7 @@ func TestURLQuerySetFunc(t *testing.T) {
 func TestURLEscapeFunc(t *testing.T) {
 	for _, tt := range []string{"foo", "foo?", "foo&"} {
 		t.Run(tt, func(t *testing.T) {
-			got, err := urlEscape.Call([]cty.Value{cty.StringVal(tt)})
+			got, err := urlEscapeFunc.Call([]cty.Value{cty.StringVal(tt)})
 			if err != nil {
 				t.Fatalf("unexpected error: %s", err)
 			}
@@ -122,6 +122,22 @@ func TestURLEscapeFunc(t *testing.T) {
 	}
 }
 
+func TestURLUserinfoFunc(t *testing.T) {
+	u := cty.StringVal("mysql://localhost:3306")
+	// Only user is provided
+	got, err := urlUserinfoFunc.Call([]cty.Value{u, cty.StringVal("user")})
+	require.NoError(t, err)
+	require.Equal(t, cty.StringVal("mysql://user@localhost:3306"), got)
+	// The password is null
+	got, err = urlUserinfoFunc.Call([]cty.Value{u, cty.StringVal("user"), cty.NullVal(cty.String)})
+	require.NoError(t, err)
+	require.Equal(t, cty.StringVal("mysql://user@localhost:3306"), got)
+	// Both user and password are provided
+	got, err = urlUserinfoFunc.Call([]cty.Value{u, cty.StringVal("user"), cty.StringVal("pass")})
+	require.NoError(t, err)
+	require.Equal(t, cty.StringVal("mysql://user:pass@localhost:3306"), got)
+}
+
 func TestMakeFileFunc(t *testing.T) {
 	fn := MakeFileFunc("testdata")
 	_, err := fn.Call([]cty.Value{cty.StringVal("foo")})