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