1 /++ High-level control of the Git version control system. 2 See: https://github.com/s-ludwig/dlibgit 3 +/ 4 module nxt.git; 5 6 @safe: 7 8 /// Depth of git clone. 9 struct Depth { 10 uint value; 11 alias value this; 12 static Depth min() pure nothrow @safe @nogc => Depth(1); 13 static Depth max() pure nothrow @safe @nogc => Depth(value.max); 14 } 15 16 /++ SHA-1 Digest +/ 17 struct SHA1Digest { 18 ubyte[20] bytes; 19 alias bytes this; 20 } 21 22 /++ (Git) Repository URL. 23 +/ 24 struct RepoURL { 25 import nxt.path : URL; 26 this(string value) pure nothrow @nogc { 27 this._value = URL(value); // don't enforce ".git" extension in `value` because it's optional 28 } 29 URL _value; /+ TODO: replace _value with url +/ 30 alias _value this; 31 } 32 33 /++ (Git) Repository URL with optional sub-directory relative path. 34 +/ 35 struct RepoURLDir { 36 import nxt.path : DirPath; 37 this(RepoURL url, DirPath subDir = DirPath.init) pure nothrow @nogc { 38 this.url = url; 39 this.subDir = subDir; 40 } 41 RepoURL url; 42 DirPath subDir; ///< Optional sub-directory in `url`. Can be both in absolute and relative. 43 } 44 45 struct RepositoryAndDir 46 { 47 import std.exception : enforce; 48 import nxt.path : DirPath, buildNormalizedPath, exists; 49 import nxt.cmd : Spawn, spawn, writeFlushed; 50 import nxt.logging : LogLevel, trace, info, warning, error; 51 import std.file : exists; 52 53 const RepoURL url; 54 const DirPath lrd; ///< Local checkout root directory. 55 LogLevel logLevel = LogLevel.warning; 56 57 this(const RepoURL url, const DirPath lrd = [], in bool echoOutErr = true) 58 { 59 this.url = url; 60 if (lrd != lrd.init) 61 this.lrd = lrd; 62 else 63 { 64 import std.path : baseName, stripExtension; 65 this.lrd = DirPath(this.url._value.str.baseName.stripExtension); 66 } 67 } 68 69 this(this) @disable; 70 71 // Actions: 72 73 auto ref setLogLevel(in LogLevel logLevel) scope pure nothrow @nogc { 74 this.logLevel = logLevel; 75 return this; 76 } 77 78 Spawn cloneOrPull(in bool recursive = true, string branch = [], in Depth depth = Depth.max) 79 { 80 if (lrd.buildNormalizedPath(DirPath(".git")).exists) 81 return pull(recursive); 82 else 83 return clone(recursive, branch, depth); 84 } 85 86 Spawn cloneOrResetHard(in bool recursive = true, string remote = "origin", string branch = [], in Depth depth = Depth.max) 87 { 88 if (lrd.buildNormalizedPath(DirPath(".git")).exists) 89 return resetHard(recursive, remote, branch); 90 else 91 return clone(recursive, branch, depth); 92 } 93 94 Spawn clone(in bool recursive = true, string branch = [], in Depth depth = Depth.max) const 95 { 96 import std.conv : to; 97 if (logLevel <= LogLevel.trace) trace("Cloning ", url, " to ", lrd); 98 return spawn(["git", "clone"] 99 ~ [url._value.str, lrd.str] 100 ~ (branch.length ? ["-b", branch] : []) 101 ~ (recursive ? ["--recurse-submodules"] : []) 102 ~ (depth != depth.max ? ["--depth", depth.to!string] : []), 103 logLevel); 104 } 105 106 Spawn pull(in bool recursive = true) 107 { 108 if (logLevel <= LogLevel.trace) trace("Pulling ", url, " to ", lrd); 109 return spawn(["git", "-C", lrd.str, "pull"] 110 ~ (recursive ? ["--recurse-submodules"] : []), 111 logLevel); 112 } 113 114 Spawn resetHard(in bool recursive = true, string remote = "origin", string branch = []) 115 { 116 if (logLevel <= LogLevel.trace) trace("Resetting hard ", url, " to ", lrd); 117 if (branch) 118 return resetHardTo(remote~"/"~branch, recursive); 119 else 120 return resetHard(recursive); 121 } 122 123 Spawn checkout(string branchName) 124 { 125 if (logLevel <= LogLevel.trace) trace("Checking out branch ", branchName, " at ", lrd); 126 return spawn(["git", "-C", lrd.str, "checkout" , branchName], logLevel); 127 } 128 129 Spawn remoteRemove(string name) 130 { 131 if (logLevel <= LogLevel.trace) trace("Removing remote ", name, " at ", lrd); 132 return spawn(["git", "-C", lrd.str, "remote", "remove", name], logLevel); 133 } 134 alias removeRemote = remoteRemove; 135 136 Spawn remoteAdd(in RepoURL url, string name) 137 { 138 if (logLevel <= LogLevel.trace) trace("Adding remote ", url, " at ", lrd, (name ? " as " ~ name : "") ~ " ..."); 139 return spawn(["git", "-C", lrd.str, "remote", "add", name, url._value.str], logLevel); 140 } 141 alias addRemote = remoteAdd; 142 143 Spawn fetch(string[] names) 144 { 145 if (logLevel <= LogLevel.trace) trace("Fetching remotes ", names, " at ", lrd); 146 if (names) 147 return spawn(["git", "-C", lrd.str, "fetch", "--multiple"] ~ names, logLevel); 148 else 149 return spawn(["git", "-C", lrd.str, "fetch"], logLevel); 150 } 151 152 Spawn fetchAll() 153 { 154 if (logLevel <= LogLevel.trace) trace("Fetching all remotes at ", lrd); 155 return spawn(["git", "-C", lrd.str, "fetch", "--all"], logLevel); 156 } 157 158 Spawn clean() 159 { 160 if (logLevel <= LogLevel.trace) trace("Cleaning ", lrd); 161 return spawn(["git", "-C", lrd.str, "clean", "-ffdx"], logLevel); 162 } 163 164 Spawn resetHard(in bool recursive = true) 165 { 166 if (logLevel <= LogLevel.trace) trace("Resetting hard ", lrd); 167 return spawn(["git", "-C", lrd.str, "reset", "--hard"] 168 ~ (recursive ? ["--recurse-submodules"] : []), logLevel); 169 } 170 171 Spawn resetHardTo(string treeish, in bool recursive = true) 172 { 173 if (logLevel <= LogLevel.trace) trace("Resetting hard ", lrd); 174 return spawn(["git", "-C", lrd.str, "reset", "--hard", treeish] 175 ~ (recursive ? ["--recurse-submodules"] : []), logLevel); 176 } 177 178 Spawn merge(string[] commits, string[] args = []) 179 { 180 if (logLevel <= LogLevel.trace) trace("Merging commits ", commits, " with flags ", args, " at ", lrd); 181 return spawn(["git", "-C", lrd.str, "merge"] ~ args ~ commits, logLevel); 182 } 183 184 Spawn revParse(string treeish, string[] args = []) const 185 { 186 if (logLevel <= LogLevel.trace) trace("Getting rev-parse of ", treeish, " at ", lrd); 187 return spawn(["git", "rev-parse", treeish] ~ args, logLevel); 188 } 189 190 // Getters: 191 192 @property: 193 194 SHA1Digest commitSHAOf(string treeish) const 195 { 196 Spawn spawn = revParse(treeish); 197 enforce(spawn.wait() == 0); 198 return typeof(return).init; /+ TODO: use `spawn.output` +/ 199 } 200 } 201 202 /++ State of repository. 203 +/ 204 struct RepoState { 205 string commitShaHex; // SHA of commit in hexadecimal form. 206 string branchOrTag; // Optional. 207 }