From a193f5387199956ddce5949280e81097fcbdefe7 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sat, 24 May 2025 21:59:01 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=B0=D0=BD=D0=B4=D1=8B:=20-=20list=20-=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D0=B0=20=D0=B1=D1=8D=D0=BA=D0=B0=D0=BF=D0=BE=D0=B2?= =?UTF-8?q?=20(=D1=81=D0=BD=D0=B8=D0=BC=D0=BA=D0=BE=D0=B2=20=D1=81=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D1=8F)=20-=20restore=20-?= =?UTF-8?q?=20=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2=20=D1=83=D0=BA?= =?UTF-8?q?=D0=B0=D0=B7=D0=B0=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=81=D0=BD?= =?UTF-8?q?=D0=B8=D0=BC=D0=BA=D0=B0=20=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE:=20-=20create=20-=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BD=D0=B8=D0=BC=D0=BA=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D1=8F=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=BE=D1=82=D0=B2?= =?UTF-8?q?=D0=B5=D1=82=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B8=20?= =?UTF-8?q?=D0=BC=D0=B5=D1=80=D0=B4=D0=B6=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=BD=D0=BE=D0=B9=20=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Текущие изменения позволяют создавать репозиторий, делать снимки состояния файлов и восстанавливать конкретное состояние --- source/snag/core/core.d | 180 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 3 deletions(-) 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"); + } } From fe55e8680fd9543d3e310f6ca5c9bde4cc4868dc Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Sat, 24 May 2025 22:01:59 +0300 Subject: [PATCH 2/2] 0.0.5 --- .vscode/launch.json | 3 ++- snag.json | 2 +- source/app.d | 30 ++++++++++++++++++++---------- source/snag/version_.d | 2 +- 4 files changed, 24 insertions(+), 13 deletions(-) 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/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";