diff --git a/.vscode/launch.json b/.vscode/launch.json index 400e7c1..03d6784 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,8 @@ "dubBuild": true, "name": "Build & Debug DUB project", "cwd": "${command:dubWorkingDirectory}", - "program": "bin/${command:dubTarget}" + "program": "bin/${command:dubTarget}", + "args": ["list"] } ] } \ No newline at end of file diff --git a/snag.json b/snag.json index f7d3e00..42a6ae2 100644 --- a/snag.json +++ b/snag.json @@ -1,6 +1,6 @@ { "git": "/tmp/testgit", - "project": "/home/alexander/Programming/new/dlang/snag/source", + "project": "/tmp/test", "email": "user@site.domain", "user": "snag", "presnag": [ diff --git a/source/app.d b/source/app.d index f3bf551..d0ff61d 100644 --- a/source/app.d +++ b/source/app.d @@ -12,6 +12,10 @@ int main(string[] args) .add(new Command("init", "Initializing the repository for storing snapshots")) .add(new Command("status", "Checking the status of tracked files")) .add(new Command("create", "Create a new backup")) + .add(new Command("list", "List of backups")) + .add(new Command("restore", "Restore to the specified snapshot state") + .add(new Argument("hash", "hash").required) + ) .add(new Option("c", "config", "Сonfiguration file path") .optional .validateEachWith( @@ -33,18 +37,24 @@ int main(string[] args) } auto snag = new Snag(config); - + import std.stdio; try { argumets - .on("init", (init) { - snag.initialize(); - }) - .on("status", (status) { - snag.status(); - }) - .on("create", (create) { - snag.create(); - }); + .on("init", init => + snag.initialize() + ) + .on("status", status => + snag.status() + ) + .on("create", create => + snag.create() + ) + .on("list", list => + snag.list() + ) + .on("restore", restore => + snag.restore(restore.arg("hash")) + ); } catch (SnagException e) { e.print(); return EXIT_FAILURE; diff --git a/source/snag/core/core.d b/source/snag/core/core.d index 06aad49..b09fb89 100644 --- a/source/snag/core/core.d +++ b/source/snag/core/core.d @@ -7,6 +7,7 @@ import std.array; import std.process; import std.algorithm; import std.string; +import std.regex; import snag.core.exception; @@ -14,6 +15,11 @@ class Snag { private string[] _baseCommand; private SnagConfig _config; + private bool isValidHash(string hash) { + auto hashPattern = ctRegex!r"^[a-fA-F0-9]{7}$"; + return !matchFirst(hash, hashPattern).empty; + } + this(SnagConfig config) { _baseCommand = format( "git --git-dir=%s --work-tree=%s", @@ -67,9 +73,9 @@ class Snag { writeln("The following list of files requires backup:"); /** - Цепочка выполняет разбивку по переводу на новую строку, - отсеивает пустые строки, перебирает в цикле имеющиеся строки, - выводит только вторую часть строки (разделенную по пробелу) + * Цепочка выполняет разбивку по переводу на новую строку, + * отсеивает пустые строки, перебирает в цикле имеющиеся строки, + * выводит только вторую часть строки (разделенную по пробелу) */ result.output.split('\n').filter!(e => !e.strip.empty).each!((e) { writefln("\t/%s", e.strip.split[1]); @@ -77,7 +83,45 @@ class Snag { } void create() { + /** + * Проверка файлов на состояние изменения + */ auto result = execute( + _baseCommand ~ ["status", "--porcelain"] + ); + if (result.status) + throw new SnagException( + "An error occurred while checking the file tracking status:\n" + ~ result.output + ); + if (!result.output.length) + throw new SnagException( + "Current file state doesn't need to be archived again" + ); + + result = execute( + _baseCommand ~ [ + "rev-parse", "--short", "HEAD" + ] + ); + if (result.status) + throw new SnagException( + "Failed to retrieve current snapshot information:\n" + ~ result.output + ); + + string currentSnapshot = result.output.strip('\n'); + + result = execute( + _baseCommand ~ [ + "checkout", + "-b", + "mod", + currentSnapshot + ] + ); + + result = execute( _baseCommand ~ ["add", "."] ); if (result.status) @@ -95,6 +139,136 @@ class Snag { ~ result.output ); + result = execute( + _baseCommand ~ ["checkout", "master"] + ); + if (result.status) + throw new SnagException( + "Failed to perform intermediate state switching:\n" + ~ result.output + ); + + result = execute( + _baseCommand ~ [ + "merge", + "-X", + "theirs", + "--squash", + "mod" + ] + ); + if (result.status) + throw new SnagException( + "Failed to retrieve changes from the new intermediate snapshot:\n" + ~ result.output + ); + + result = execute( + _baseCommand ~ ["commit", "-m", "test"] + ); + if (result.status) + throw new SnagException( + "Failed to create backup after acquiring new intermediate snapshot:\n" + ~ result.output + ); + + result = execute( + _baseCommand ~ ["branch", "-D", "mod"] + ); + if (result.status) + throw new SnagException( + "Error while deleting temporary intermediate snapshot:\n" + ~ result.output + ); + writeln("Backup was created successfully"); } + + void list() { + auto result = execute( + _baseCommand ~ [ + "rev-parse", "--short", "HEAD" + ] + ); + if (result.status) + throw new SnagException( + "Failed to retrieve current snapshot information:\n" + ~ result.output + ); + + string current = result.output.strip('\n'); + + result = execute( + _baseCommand ~ [ + "log", + "--all", + "--date=format:%Y.%m.%d %H:%M", + "--pretty=format:%ad\t%h" + ] + ); + if (result.status) + throw new SnagException( + "Failed to retrieve the list of snapshots:\n" + ~ result.output + ); + + result.output.split('\n').each!((e) { + auto eArray = e.split('\t'); + if (current == eArray[1]) + writefln(" >\t%s\t%s", eArray[0], eArray[1]); + else + writefln("\t%s\t%s", eArray[0], eArray[1]); + }); + } + + void restore(string hash) { + if (!isValidHash(hash)) + throw new SnagException( + "Invalid snapshot hash provided" + ); + + auto result = execute( + _baseCommand ~ ["status", "--porcelain"] + ); + if (result.status) + throw new SnagException( + "An error occurred while checking the file tracking status:\n" + ~ result.output + ); + + if (result.output.length) { + result = execute( + _baseCommand ~ ["restore", "."] + ); + if (result.status) + throw new SnagException( + "Failed to reset file changes state:\n" + ~ result.output + ); + } + + result = execute( + _baseCommand ~ [ + "rev-parse", hash + ] + ); + if (result.status) + throw new SnagException( + "This snapshot is not available in the archive:\n" + ~ result.output.split('\n')[0] + ); + + result = execute( + _baseCommand ~ [ + "checkout", hash + ] + ); + if (result.status) + throw new SnagException( + "Failed to restore the snapshot state %s:\n" + .format(hash, result.output) + ); + + writeln("Backup was restored successfully"); + } } diff --git a/source/snag/version_.d b/source/snag/version_.d index 2465932..2d93c85 100644 --- a/source/snag/version_.d +++ b/source/snag/version_.d @@ -1,3 +1,3 @@ module snag.version_; -enum snagVersion = "0.0.4"; +enum snagVersion = "0.0.5";