OXYGENHUB
# 201f900d7ece oxygen / src / cli / workspace.rs
6.6 KiB · Rust · last changed Packed storage: zstd packs with sorted indexes, oxy gc, auto-pack at 4096 loose objects; README 1 month ago source blame history raw
1
//! Commands that act on the local repository and working copy.
2
3
use super::format::{deps_line, plural, short_id, signature_line, when};
4
use super::open;
5
use anyhow::Result;
6
use oxy_core::repo::{render_change, ChangeStatus, Repo};
7
use std::path::PathBuf;
8
9
pub fn init(path: Option<PathBuf>) -> Result<i32> {
10
    let path = path.unwrap_or(std::env::current_dir()?);
11
    let repo = Repo::init(&path)?;
12
    println!("Initialized oxy repository in {}", repo.root().display());
13
    println!("Bookmark: {}   Author: {} <{}>", repo.config.bookmark, repo.config.author_name, repo.config.author_email);
14
    Ok(0)
15
}
16
17
fn status_tag(s: ChangeStatus) -> &'static str {
18
    match s {
19
        ChangeStatus::Added => "A",
20
        ChangeStatus::Modified => "M",
21
        ChangeStatus::Removed => "D",
22
        ChangeStatus::ModeChanged => "X",
23
        ChangeStatus::ConflictEdited => "C!",
24
        ChangeStatus::Resolved => "R",
25
    }
26
}
27
28
pub fn status() -> Result<i32> {
29
    let repo = open()?;
30
    let snap = repo.snapshot("oxy status")?;
31
    let heads = repo.bookmark_heads()?;
32
    println!("Working copy: {} on {} ({})", snap.wc.change_id.short(8), repo.config.bookmark, plural(heads.len(), "head"));
33
    if snap.statuses.is_empty() {
34
        println!("The working copy is clean.");
35
    }
36
    for s in &snap.statuses {
37
        println!("{:2} {}", status_tag(s.status), s.path);
38
    }
39
    if !snap.base_snapshot.is_conflicted() {
40
        return Ok(0);
41
    }
42
    println!("\nConflicts in the base state (edit the marked files, or the .oxy-conflict-N candidates, then commit):");
43
    for c in &snap.base_snapshot.tree.conflicts {
44
        println!("  tree: {c:?}");
45
    }
46
    for (p, f) in &snap.base_snapshot.files {
47
        if f.is_conflicted() {
48
            let state = match snap.statuses.iter().find(|s| s.path == *p).map(|s| s.status) {
49
                Some(ChangeStatus::Resolved) => "resolved in working copy",
50
                Some(ChangeStatus::ConflictEdited) => "markers remain",
51
                _ => "unresolved",
52
            };
53
            println!("  {p}  ({state})");
54
        }
55
    }
56
    Ok(2)
57
}
58
59
pub fn commit(message: &str, depends_on: &[String]) -> Result<i32> {
60
    let repo = open()?;
61
    let mut deps = Vec::new();
62
    for d in depends_on {
63
        deps.push(repo.resolve_change(d)?);
64
    }
65
    let h = repo.commit(message, &deps)?;
66
    let c = repo.store().get_change(&h)?;
67
    println!("Committed {} #{}: {}", short_id(&c), h.short(12), message.lines().next().unwrap_or(""));
68
    Ok(0)
69
}
70
71
pub fn log() -> Result<i32> {
72
    let repo = open()?;
73
    repo.snapshot("oxy log")?;
74
    for e in repo.log()? {
75
        let c = &e.change;
76
        let mut flags = Vec::new();
77
        if e.is_working_copy {
78
            flags.push("working copy");
79
        }
80
        if e.is_head {
81
            flags.push("head");
82
        }
83
        let flags = if flags.is_empty() { String::new() } else { format!(" ({})", flags.join(", ")) };
84
        println!("{} #{}{}", short_id(c), e.hash.short(12), flags);
85
        println!("    {}", when(c.hlc.wall_ms));
86
        if !c.deps.is_empty() {
87
            println!("    deps: {}", deps_line(c));
88
        }
89
        let msg = if c.message.is_empty() { "(no description)" } else { &c.message };
90
        for line in msg.lines() {
91
            println!("    {line}");
92
        }
93
        println!();
94
    }
95
    Ok(0)
96
}
97
98
pub fn diff() -> Result<i32> {
99
    let repo = open()?;
100
    let snap = repo.snapshot("oxy diff")?;
101
    print!("{}", render_change(&snap.wc, &snap.base, &snap.base_snapshot));
102
    Ok(0)
103
}
104
105
pub fn show(change: &str) -> Result<i32> {
106
    let repo = open()?;
107
    repo.snapshot("oxy show")?;
108
    let h = repo.resolve_change(change)?;
109
    let c = repo.store().get_change(&h)?;
110
    println!("change {} #{}", short_id(&c), h);
111
    println!("kind:   {:?}", c.kind);
112
    println!("date:   {}", when(c.hlc.wall_ms));
113
    println!("deps:   {}", deps_line(&c));
114
    println!("sig:    {}", signature_line(&repo, &h)?);
115
    println!("\n{}\n", if c.message.is_empty() { "(no description)" } else { &c.message });
116
    print!("{}", repo.describe_change(&h)?);
117
    Ok(0)
118
}
119
120
pub fn checkout(changes: &[String], discard: bool) -> Result<i32> {
121
    let repo = open()?;
122
    let heads = if changes.is_empty() {
123
        repo.bookmark_heads()?
124
    } else {
125
        changes.iter().map(|c| repo.resolve_change(c)).collect::<Result<Vec<_>, _>>()?
126
    };
127
    let spec = if changes.is_empty() { repo.config.bookmark.clone() } else { changes.join(" ") };
128
    let r = repo.checkout(&heads, discard, &format!("oxy checkout {spec}"))?;
129
    println!("Working copy now on {spec}: {} written, {} removed", plural(r.written, "file"), r.removed);
130
    if r.conflicted {
131
        println!("This state has conflicts; see oxy status.");
132
    }
133
    Ok(0)
134
}
135
136
pub fn resolve() -> Result<i32> {
137
    let repo = open()?;
138
    let snap = repo.snapshot("oxy resolve")?;
139
    if !snap.base_snapshot.is_conflicted() {
140
        println!("The working copy's base has no conflicts.");
141
        return Ok(0);
142
    }
143
    let mut remaining = 0;
144
    for (p, f) in &snap.base_snapshot.files {
145
        if !f.is_conflicted() {
146
            continue;
147
        }
148
        match snap.statuses.iter().find(|s| s.path == *p).map(|s| s.status) {
149
            Some(ChangeStatus::Resolved) => println!("resolved   {p}"),
150
            Some(ChangeStatus::ConflictEdited) => {
151
                remaining += 1;
152
                println!("markers    {p}");
153
            }
154
            _ => {
155
                remaining += 1;
156
                println!("unresolved {p}");
157
            }
158
        }
159
    }
160
    for c in &snap.base_snapshot.tree.conflicts {
161
        remaining += 1;
162
        println!("tree       {c:?}");
163
    }
164
    if remaining == 0 {
165
        println!("All conflicts are resolved; `oxy commit` will record the resolution.");
166
        Ok(0)
167
    } else {
168
        Ok(2)
169
    }
170
}
171
172
pub fn undo() -> Result<i32> {
173
    let repo = open()?;
174
    let op = repo.undo()?;
175
    println!("Undid the last operation; now at: {}", op.command);
176
    Ok(0)
177
}
178
179
pub fn op_log() -> Result<i32> {
180
    let repo = open()?;
181
    for (i, (r, op)) in repo.op_log()?.into_iter().enumerate() {
182
        println!("{}{} {}  {}", if i == 0 { "@ " } else { "  " }, r.short(12), when(op.hlc.wall_ms), op.command);
183
    }
184
    Ok(0)
185
}
186
187
pub fn gc() -> Result<i32> {
188
    let repo = open()?;
189
    let r = repo.gc()?;
190
    if r.objects == 0 {
191
        println!("Nothing to pack; {} pack(s) present.", repo.store().pack_count());
192
    } else {
193
        println!(
194
            "Packed {} into one pack: {} bytes -> {} bytes ({:.0}%); {} pack(s) present.",
195
            plural(r.objects, "object"),
196
            r.raw_bytes,
197
            r.packed_bytes,
198
            100.0 * r.packed_bytes as f64 / r.raw_bytes.max(1) as f64,
199
            repo.store().pack_count()
200
        );
201
    }
202
    Ok(0)
203
}