Skip to content

Commit feff017

Browse files
authored
chore: refactor config file structure and layout (#54)
Changes: - Consolidates the configuration and lock file into a single file. - Update design doc to match new config file design - Refactored a lot of code and restructured `config.rs` to be more readable, otherwise it would have been a massive single mod.
1 parent 8e8de3d commit feff017

24 files changed

Lines changed: 1225 additions & 1374 deletions

‎.embd/config.toml‎

Lines changed: 0 additions & 5 deletions
This file was deleted.

‎.embd/embd.lock‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

‎.embd/embd.toml‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[infra.metadata]
2+
remote = "git@github.com:ptsouchlos/infra.git"
3+
commit_hash = "79932afc417d21900038dcd8f8154c3661c15f76"
4+
folder = "infra"
5+
allow_untracked = false
6+
include = [
7+
"just/**",
8+
"rust/**",
9+
]
10+
11+
[infra.files]
12+
"just/shells.just" = "sha256:ca031066da941b192b18859a4c8ebda1ecfe91ff14f57fe31489c6e83cb3bfa7"
13+
"rust/just/clippy.just" = "sha256:5ce758826bbd8f4f3ffd626792edb06ab1fe41c591f7cd099c59336c46a1081f"
14+
"rust/just/format.just" = "sha256:cb2e821139f1e314f5b87e4282bb75524d9d90cdd0dee1eaa524b8fa4b84f762"

‎.justfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Import shared just recipes
2-
import "infra/rust/just/shells.just"
2+
import "infra/just/shells.just"
33
import "infra/rust/just/clippy.just"
44
import "infra/rust/just/format.just"
55

‎README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ cargo install --path .
2929
embd add -l <repo link>.git -f <local folder>
3030
```
3131

32-
This will clone the repository to the given folder and create a `config.toml` and `embd.lock` file in the `.embd` folder. These files should be commited to VCS. You can also filter the contents of the repository using `-i` and `-e` flags. These use glob filters to include or exclude certain folders or files. For example, to exclude all Markdown and include all text files, you could the following:
32+
This will clone the repository to the given folder and create an `embd.toml` file in the `.embd` folder. This file tracks both the pinned remote/commit for each embed and the hashes of the files it manages, and should be commited to VCS. You can also filter the contents of the repository using `-i` and `-e` flags. These use glob filters to include or exclude certain folders or files. For example, to exclude all Markdown and include all text files, you could the following:
3333

3434
```bash
3535
embd add -l <repo> -f <folder> -i "**.txt" -e "**.md"
@@ -43,7 +43,7 @@ To update all embedded projects, run:
4343
embd update
4444
```
4545

46-
To update a specific project, use the name of the repo. This corresponds to the key of the projects entry in the `.embd/config.toml` file:
46+
To update a specific project, use the name of the repo. This corresponds to the key of the projects entry in the `.embd/embd.toml` file:
4747

4848
```bash
4949
embd update infra
@@ -55,7 +55,7 @@ To update a project to a new commit, tag or branch, use the `-r` or `--rev` flag
5555
embd update infra --rev abcd1234
5656
```
5757

58-
This will update the files on disk and update the commit hash tracked in the config file and lock file. In addition, the entries in the lock file for the given project will also be updated. Updates can also be forced using `--force` and untracked files can be removed using `--overwrite`. See `embd update -h` for more details.
58+
This will update the files on disk and update the commit hash and file manifest tracked for that project in `embd.toml`. Updates can also be forced using `--force` and untracked files can be removed using `--overwrite`. See `embd update -h` for more details.
5959

6060
### Check Status of Embeds
6161

‎docs/design.md‎

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# Design of `embd`
22

3-
The basic goal of `embd` is to provide a simple way to "embed" one git repository into another. The prime source of inspiration for this project is the [beman-submodule](https://github.com/bemanproject/beman-submodule) tool, which is implemented in Python and is available on PyPI. `embd`, on the other hand, is implemented in Rust and will be available as a static binary which provides many options for distibution and installation.
3+
The basic goal of `embd` is to provide a simple way to "embed" one git repository into another. The prime source of inspiration for this project is the [beman-submodule](https://github.com/bemanproject/beman-submodule) tool, which is implemented in Python and is available on PyPI. `embd`, on the other hand, is implemented in Rust and will be available as a static binary which provides many options for distribution and installation.
44

5-
An additional goal of `embd` is to be more general purpose than `beman-submodule` which is more closely tied to the goals and setup that the various `Beman` projects use.
5+
An additional goal of `embd` is to be more general purpose than `beman-submodule`, which is more closely tied to the goals and setup that the various `Beman` projects use.
66

77
## Why?
88

9-
Both `beman-submodule` and `embd` aim to be alternatives to either [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) and [git subtree](https://www.atlassian.com/git/tutorials/git-subtree). [Eddie Nolan](https://www.ednolan.com) put together a set of [slides](https://www.ednolan.com/toolchains_slides.pdf) to show the differences and limitations of both `git subtree` and `git submodule`, so I won't go into that too much here. The gist is that `git submodule` provides a subpar user experience (users have to remember to run `git submodule update --init`) and `git subtree`s force merge commits in your history.
9+
Both `beman-submodule` and `embd` aim to be alternatives to either [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) or [git subtree](https://www.atlassian.com/git/tutorials/git-subtree). [Eddie Nolan](https://www.ednolan.com) put together a set of [slides](https://www.ednolan.com/toolchains_slides.pdf) to show the differences and limitations of both `git subtree` and `git submodule`, so I won't go into that too much here. The gist is that `git submodule` provides a subpar user experience (users have to remember to run `git submodule update --init`) and `git subtree`s force merge commits in your history.
1010

1111
## What's the solution?
1212

13-
The general idea is to pull the dependant repository as source into the parent repository. This puts the burden of keeping dependencies up to date on the maintainer. `embd` helps in making that process easier for the maintainer.
13+
The general idea is to pull the dependent repository as source into the parent repository. This puts the burden of keeping dependencies up to date on the maintainer. `embd` helps in making that process easier for the maintainer.
1414

1515
## Design
1616

@@ -22,22 +22,26 @@ The general idea is to pull the dependant repository as source into the parent r
2222
| `update` | Update all embeds to match the config file. | `rev`: Specify the commit, tag or branch to advance to \n `force`: Overwrite any local modification \n `overwrite`: Delete all untracked files (requries `force`) \n `quiet`: Print summaries, not each file updates. |
2323
| `status` | Show the status of all embeds. | `quiet`: Should summaries per embed, not per file. |
2424

25-
### Config File
25+
### Configuration File
2626

27-
The configuration file describes all the embedded sources.
27+
`embd` keeps a single consolidated file, `.embd/embd.toml`, describing all the embedded sources. Each entry has a `metadata` table which includes data like the remote repository URL, commit, folder, and include/exclude filters. The config entry also includes a `files` table of sha256 hashes per tracked file. This is used to detect local drift without having to re-pull or clone files from the remote.
2828

2929
```toml
30-
[repo1]
31-
remote="https://example.git"
32-
commit_hash=123abcd1234
33-
folder=/example
34-
35-
[repo2]
36-
remote="https://example2.git"
37-
commit_hash=123abcd1234
38-
folder=/example2
30+
[repo1.metadata]
31+
remote = "https://example.git"
32+
commit_hash = "123abcd1234"
33+
folder = "example"
34+
allow_untracked = false
35+
36+
[repo1.files]
37+
"a.txt" = "sha256:..."
38+
39+
[repo2.metadata]
40+
remote = "https://example2.git"
41+
commit_hash = "123abcd1234"
42+
folder = "example2"
43+
allow_untracked = false
44+
45+
[repo2.files]
46+
"b.txt" = "sha256:..."
3947
```
40-
41-
### Lockfile
42-
43-
In addition to the config file, `embd` maintains a lockfile to check for differences in the local files and changes to the subfolder an embed is put into. This serves as an easy way to verify file status without pull the files again from the remote location.

‎infra/README.md‎

Lines changed: 0 additions & 8 deletions
This file was deleted.

‎src/commands/add.rs‎

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ use anyhow::{Context, Result, bail};
44
use tempfile::tempdir;
55

66
use crate::color;
7-
use crate::config::{self, EmbdEntry};
7+
use crate::config::{self, EmbdEntry, FileLocks, Metadata};
88
use crate::filter::Filter;
9-
use crate::lockfile::{self, LockEntry};
109
use crate::{filesystem, git, paths};
1110

1211
/// Input arguments for the `add` command.
@@ -101,38 +100,34 @@ pub(crate) fn execute(args: AddArgs) -> Result<()> {
101100
// so we never leave on-disk state without a matching config entry.
102101
filesystem::copy_dir(tmp_dir.path(), &folder_abs, &filter)?;
103102

104-
let lock_path = paths::lock_path(&root);
105-
106103
let result = (|| -> Result<()> {
107-
let lock_entry = LockEntry::build_from_path(&folder_abs, commit_hash.clone())?;
108-
let mut lock = lockfile::Lockfile::load_or_default(&lock_path)?;
109-
lock.upsert(repo_name.clone(), lock_entry);
110-
lock.save(&lock_path)?;
104+
// Hash the destination folder as-copied (not just what the filter
105+
// allowed) so a pre-existing --allow-untracked folder's contents are
106+
// reflected too, matching what `copy_dir` actually left on disk.
107+
let files = FileLocks::build_from_path(&folder_abs)?;
111108
config.insert(
112109
repo_name.clone(),
113110
EmbdEntry {
114-
remote: link,
115-
commit_hash,
116-
folder: folder_rel,
117-
allow_untracked: args.allow_untracked,
118-
include,
119-
exclude,
111+
metadata: Metadata {
112+
remote: link,
113+
commit_hash,
114+
folder: folder_rel,
115+
allow_untracked: args.allow_untracked,
116+
include,
117+
exclude,
118+
},
119+
files,
120120
},
121121
)?;
122122
config.save(&config_path)?;
123123
Ok(())
124124
})();
125125

126126
if let Err(e) = result {
127-
// If there was an error, try to rollback to the previous state.
127+
// If there was an error, try to rollback to the previous state. Nothing
128+
// is written to config.toml unless the whole closure above succeeds, so
129+
// there's no separate on-disk manifest entry to clean up here.
128130
rollback(&folder_abs, folder_existed, args.allow_untracked);
129-
// Drop the entry we may have written to the lock file, leaving any other
130-
// entries untouched.
131-
if let Ok(mut lock) = lockfile::Lockfile::load(&lock_path)
132-
&& lock.remove(&repo_name).is_some()
133-
{
134-
let _ = lock.save(&lock_path);
135-
}
136131
// Propagate the error up to the caller
137132
return Err(e);
138133
}

‎src/commands/status.rs‎

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use anyhow::Result;
22

33
use crate::color;
44
use crate::commands::common::select_entries;
5-
use crate::config;
6-
use crate::lockfile::{self, EntryReport, EntryState, FileChange, Lockfile};
5+
use crate::config::{self, EntryReport, EntryState, FileChange};
76
use crate::paths;
87

98
#[derive(clap::Args, Debug)]
@@ -19,12 +18,11 @@ pub(crate) fn execute(args: StatusArgs) -> Result<()> {
1918
let root = paths::find_git_root()?;
2019
let config_path = paths::config_path(&root);
2120
let config = config::load_or_default(&config_path)?;
22-
let lock = Lockfile::load_or_default(&paths::lock_path(&root))?;
2321
let selected = select_entries(&config, &args.names)?;
2422

2523
let mut any_drift = false;
2624
for (name, entry) in selected {
27-
let report = lockfile::inspect_entry(&root, name, entry, lock.get(name));
25+
let report = config::inspect_entry(&root, name, entry);
2826
any_drift |= report.has_drift();
2927
print_report(&report, args.quiet);
3028
}
@@ -45,16 +43,10 @@ pub(crate) fn print_report(report: &EntryReport, quiet: bool) {
4543
println!("{header}: {}", color::bad("folder missing"));
4644
return;
4745
}
48-
EntryState::Missing => {
49-
println!("{header}: {}", color::bad("missing from lock file"));
50-
return;
51-
}
5246
EntryState::Compared => {}
5347
}
5448

55-
let summary = if let Some((local, wanted)) = &report.stale {
56-
color::warn(&format!("stale (folder at {local}, config wants {wanted})"))
57-
} else if report.changes.is_empty() {
49+
let summary = if report.changes.is_empty() {
5850
color::ok("clean")
5951
} else {
6052
color::warn("drift")

0 commit comments

Comments
 (0)