Compare commits
No commits in common. "016455d8f2ed5d906c0f8d330c103df6f5deaad0" and "db9a6be9f439ee4f41096ba02cbe6d5238e3c7d1" have entirely different histories.
016455d8f2
...
db9a6be9f4
7 changed files with 5 additions and 267 deletions
12
snag.json
12
snag.json
|
@ -10,15 +10,5 @@
|
||||||
"postsnag": [
|
"postsnag": [
|
||||||
"/usr/bin/ls",
|
"/usr/bin/ls",
|
||||||
"/usr/local/bin/script.sh"
|
"/usr/local/bin/script.sh"
|
||||||
],
|
]
|
||||||
"rules": {
|
|
||||||
"tracking": [
|
|
||||||
"/etc/*.conf"
|
|
||||||
],
|
|
||||||
"ignore": [
|
|
||||||
"/usr/exit/.gitignore",
|
|
||||||
"/usr/exit/dd",
|
|
||||||
"/file1"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
37
source/app.d
37
source/app.d
|
@ -21,23 +21,6 @@ int main(string[] args)
|
||||||
.optional
|
.optional
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.add(new Command("rules", "Tracking rules")
|
|
||||||
.add(new Command("update", "Update rules")
|
|
||||||
.add(new Flag("r", "remove", "Removing from tracking the found ignored files")
|
|
||||||
.name("remove")
|
|
||||||
.optional
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.add(new Command("reset", "Reset rules (restores rules to pre-change state)"))
|
|
||||||
.add(new Command("clear", "Clear rules"))
|
|
||||||
.add(new Command("save", "Save rules"))
|
|
||||||
.add(new Command("show", "Show rules")
|
|
||||||
.add(new Flag("c", "config", "Show rules from the configuration file")
|
|
||||||
.name("config")
|
|
||||||
.optional
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.add(new Command("status", "Checking the status of tracked files"))
|
.add(new Command("status", "Checking the status of tracked files"))
|
||||||
.add(new Command("diff", "Show changed data"))
|
.add(new Command("diff", "Show changed data"))
|
||||||
.add(new Command("import", "Import snapshot from a tar.gz archive")
|
.add(new Command("import", "Import snapshot from a tar.gz archive")
|
||||||
|
@ -177,26 +160,6 @@ int main(string[] args)
|
||||||
i.option("author", ""),
|
i.option("author", ""),
|
||||||
i.option("email", "")
|
i.option("email", "")
|
||||||
)
|
)
|
||||||
)
|
|
||||||
.on("rules", (r) {
|
|
||||||
auto rules = new SnagRules(config);
|
|
||||||
r
|
|
||||||
.on("update", update =>
|
|
||||||
rules.update(update.flag("remove"))
|
|
||||||
)
|
|
||||||
.on("reset", reset =>
|
|
||||||
rules.reset()
|
|
||||||
)
|
|
||||||
.on("clear", clear =>
|
|
||||||
rules.clear()
|
|
||||||
)
|
|
||||||
.on("save", clear =>
|
|
||||||
rules.save()
|
|
||||||
)
|
|
||||||
.on("show", show =>
|
|
||||||
rules.show(show.flag("config"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
} catch (SnagException e) {
|
} catch (SnagException e) {
|
||||||
e.print();
|
e.print();
|
||||||
|
|
|
@ -5,8 +5,6 @@ import std.file;
|
||||||
import std.path;
|
import std.path;
|
||||||
import std.string;
|
import std.string;
|
||||||
import snag.lib;
|
import snag.lib;
|
||||||
import std.algorithm;
|
|
||||||
import std.array;
|
|
||||||
|
|
||||||
import snag.config.exception;
|
import snag.config.exception;
|
||||||
|
|
||||||
|
@ -15,8 +13,6 @@ class SnagConfig {
|
||||||
private string _project;
|
private string _project;
|
||||||
private string _email;
|
private string _email;
|
||||||
private string _author;
|
private string _author;
|
||||||
private string[] _tracking;
|
|
||||||
private string[] _ignore;
|
|
||||||
|
|
||||||
this(string configFile) {
|
this(string configFile) {
|
||||||
string jsonText;
|
string jsonText;
|
||||||
|
@ -97,34 +93,10 @@ class SnagConfig {
|
||||||
throw new SnagConfigException(
|
throw new SnagConfigException(
|
||||||
"The \"author\" parameter must contain an author name"
|
"The \"author\" parameter must contain an author name"
|
||||||
);
|
);
|
||||||
|
|
||||||
if ("rules" in jsonData) {
|
|
||||||
if (jsonData["rules"].type != JSONType.object)
|
|
||||||
throw new SnagConfigException(
|
|
||||||
"The \"rules\" parameter must be an object"
|
|
||||||
);
|
|
||||||
auto rules = jsonData["rules"];
|
|
||||||
if ("tracking" in rules) {
|
|
||||||
if (rules["tracking"].type != JSONType.array)
|
|
||||||
throw new SnagConfigException(
|
|
||||||
"The \"tracking\" parameter must be an array containing a set of paths to tracked files"
|
|
||||||
);
|
|
||||||
_tracking = rules["tracking"].array.map!(item => item.str).array;
|
|
||||||
}
|
|
||||||
if ("ignore" in rules) {
|
|
||||||
if (rules["ignore"].type != JSONType.array)
|
|
||||||
throw new SnagConfigException(
|
|
||||||
"The \"ignore\" parameter must contain a gitignore rule"
|
|
||||||
);
|
|
||||||
_ignore = rules["ignore"].array.map!(item => item.str).array;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@property string git() const { return _git; }
|
@property string git() const { return _git; }
|
||||||
@property string project() const { return _project; }
|
@property string project() const { return _project; }
|
||||||
@property string email() const { return _email; }
|
@property string email() const { return _email; }
|
||||||
@property string author() const { return _author; }
|
@property string author() const { return _author; }
|
||||||
@property const(string[]) tracking() const { return _tracking; }
|
|
||||||
@property const(string[]) ignore() const { return _ignore; }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import std.path;
|
||||||
|
|
||||||
import snag.lib;
|
import snag.lib;
|
||||||
import snag.core.exception;
|
import snag.core.exception;
|
||||||
import snag.core.rules;
|
|
||||||
|
|
||||||
class Snag {
|
class Snag {
|
||||||
private string[] _baseCommand;
|
private string[] _baseCommand;
|
||||||
|
@ -45,22 +44,6 @@ class Snag {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string gitStatus(string shortStatus, bool formatted = false) {
|
|
||||||
immutable string[string] statusMap = [
|
|
||||||
"??": "Untracked",
|
|
||||||
"A": "Added",
|
|
||||||
"M": "Modified",
|
|
||||||
"D": "Deleted",
|
|
||||||
"R": "Renamed",
|
|
||||||
"C": "Copied",
|
|
||||||
"U": "Unmerged",
|
|
||||||
"T": "Type changed",
|
|
||||||
"!": "Ignored"
|
|
||||||
];
|
|
||||||
string fullStatus = statusMap.get(shortStatus, shortStatus);
|
|
||||||
return formatted && fullStatus.length < 8 ? fullStatus ~ "\t" : fullStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
void initialize(bool force) {
|
void initialize(bool force) {
|
||||||
auto result = execute(_baseCommand ~ ["rev-parse", "--git-dir"]);
|
auto result = execute(_baseCommand ~ ["rev-parse", "--git-dir"]);
|
||||||
!force && !result.status &&
|
!force && !result.status &&
|
||||||
|
@ -85,9 +68,6 @@ class Snag {
|
||||||
["config", "user.name", _config.author],
|
["config", "user.name", _config.author],
|
||||||
"A Git repository initialization error occurred"
|
"A Git repository initialization error occurred"
|
||||||
);
|
);
|
||||||
|
|
||||||
(new SnagRules(_config)).create();
|
|
||||||
|
|
||||||
writeln(
|
writeln(
|
||||||
"The Git repository has been initialized successfully: ",
|
"The Git repository has been initialized successfully: ",
|
||||||
_config.git
|
_config.git
|
||||||
|
@ -104,11 +84,9 @@ class Snag {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeln("The following list of files requires backup:");
|
writeln("The following list of files requires backup:");
|
||||||
result.output.split('\n')[0..$-1].map!(e =>
|
result.output.split('\n').filter!(e => !e.strip.empty).each!((e) {
|
||||||
e.strip.split
|
writefln("\t/%s", e.strip.split[1]);
|
||||||
).each!(e =>
|
});
|
||||||
writefln("\t%s\t/%s", gitStatus(e[0], true), e[1])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void create(string comment, string author, string email) {
|
void create(string comment, string author, string email) {
|
||||||
|
|
|
@ -2,4 +2,3 @@ module snag.core;
|
||||||
|
|
||||||
public import snag.core.core;
|
public import snag.core.core;
|
||||||
public import snag.core.exception;
|
public import snag.core.exception;
|
||||||
public import snag.core.rules;
|
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
module snag.core.rules;
|
|
||||||
|
|
||||||
import snag.config;
|
|
||||||
import snag.core.exception;
|
|
||||||
import std.algorithm;
|
|
||||||
import std.array;
|
|
||||||
import std.path;
|
|
||||||
import std.stdio;
|
|
||||||
import std.string;
|
|
||||||
import std.conv;
|
|
||||||
import std.container;
|
|
||||||
import std.process;
|
|
||||||
import std.file;
|
|
||||||
|
|
||||||
class SnagRules {
|
|
||||||
private string[] _rules;
|
|
||||||
private SnagConfig _config;
|
|
||||||
private string[] _baseCommand;
|
|
||||||
private string _gitignore;
|
|
||||||
private string _gitignoreBak;
|
|
||||||
|
|
||||||
private auto git(string[] command, string message, string separator = ":\n\t") {
|
|
||||||
auto result = execute(_baseCommand ~ command);
|
|
||||||
if (result.status)
|
|
||||||
throw new SnagException(
|
|
||||||
message ~ separator ~ result.output.split('\n')[0]
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string[] generateGitignoreRules(string finalPath) {
|
|
||||||
string[] rules;
|
|
||||||
string[] parts = finalPath.split("/").filter!(p => !p.empty).array;
|
|
||||||
|
|
||||||
if (parts.length == 0) return rules;
|
|
||||||
|
|
||||||
rules ~= "/*";
|
|
||||||
rules ~= "!/" ~ parts[0];
|
|
||||||
rules ~= "/" ~ parts[0] ~ "/*";
|
|
||||||
|
|
||||||
if (parts.length > 1) {
|
|
||||||
string currentPath = "/" ~ parts[0];
|
|
||||||
foreach (i; 1 .. parts.length) {
|
|
||||||
currentPath ~= "/" ~ parts[i];
|
|
||||||
rules ~= "!" ~ currentPath;
|
|
||||||
if (i < parts.length.to!int - 1) {
|
|
||||||
rules ~= currentPath ~ "/*";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rules;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generate() {
|
|
||||||
string[] rules;
|
|
||||||
auto tempRules = new RedBlackTree!string;
|
|
||||||
|
|
||||||
_config.tracking.each!(
|
|
||||||
track => rules ~= generateGitignoreRules(track)
|
|
||||||
);
|
|
||||||
|
|
||||||
rules.each!((rule) {
|
|
||||||
if (rule in tempRules) return;
|
|
||||||
tempRules.insert(rule);
|
|
||||||
_rules ~= rule;
|
|
||||||
});
|
|
||||||
|
|
||||||
_rules ~= _config.ignore;
|
|
||||||
}
|
|
||||||
|
|
||||||
this(SnagConfig config) {
|
|
||||||
_config = config;
|
|
||||||
_gitignore = config.git.buildPath("info/exclude");
|
|
||||||
_gitignoreBak = _gitignore ~ ".bak";
|
|
||||||
|
|
||||||
_baseCommand = format(
|
|
||||||
"git --git-dir=%s --work-tree=%s",
|
|
||||||
config.git, config.project
|
|
||||||
).split();
|
|
||||||
|
|
||||||
generate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restoreUntracked() {
|
|
||||||
git(
|
|
||||||
["diff", "--cached", "--name-only"],
|
|
||||||
"Failed to retrieve the list of files removed from tracking"
|
|
||||||
).output.split('\n')[0..$-1].each!(
|
|
||||||
file => git(
|
|
||||||
["add", "-f", file],
|
|
||||||
"Failed to restore the file to tracking"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeUntracked() {
|
|
||||||
git(
|
|
||||||
["ls-files", "-i", "-c", "--exclude-standard"],
|
|
||||||
"Failed to get the list of files to remove from tracking"
|
|
||||||
).output.split('\n')[0..$-1].each!(
|
|
||||||
file => git(
|
|
||||||
["rm", "--cached", file],
|
|
||||||
"Failed to remove file from tracking"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void create() {
|
|
||||||
auto file = File(_gitignore, "w");
|
|
||||||
_rules.each!(rule => file.writeln(rule));
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void update(bool remove) {
|
|
||||||
if (!_gitignoreBak.exists) {
|
|
||||||
readText(_gitignore).splitLines().equal(_rules) &&
|
|
||||||
throw new SnagException(
|
|
||||||
"Rule configuration is up-to-date and doesn't require updating"
|
|
||||||
);
|
|
||||||
copy(_gitignore, _gitignoreBak);
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreUntracked();
|
|
||||||
create();
|
|
||||||
|
|
||||||
remove && removeUntracked();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
!_gitignoreBak.exists &&
|
|
||||||
throw new SnagException(
|
|
||||||
"No rule changes to reset"
|
|
||||||
);
|
|
||||||
restoreUntracked();
|
|
||||||
rename(_gitignoreBak, _gitignore);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
!readText(_gitignore).splitLines().length &&
|
|
||||||
throw new SnagException(
|
|
||||||
"The configuration has no rules"
|
|
||||||
);
|
|
||||||
!_gitignoreBak.exists && copy(_gitignore, _gitignoreBak);
|
|
||||||
restoreUntracked();
|
|
||||||
File(_gitignore, "w").close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void show(bool config) {
|
|
||||||
if (config)
|
|
||||||
_rules.join('\n').writeln;
|
|
||||||
else
|
|
||||||
readText(_gitignore).write;
|
|
||||||
}
|
|
||||||
|
|
||||||
void save() {
|
|
||||||
if (!_gitignoreBak.exists) {
|
|
||||||
writeln("The rules are up to date");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
remove(_gitignoreBak);
|
|
||||||
writeln("The rules have been saved");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,3 @@
|
||||||
module snag.version_;
|
module snag.version_;
|
||||||
|
|
||||||
enum snagVersion = "0.0.11";
|
enum snagVersion = "0.0.10";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue