|
1 | 1 | using System; |
| 2 | +using System.IO; |
2 | 3 | using System.Linq; |
3 | 4 | using System.Globalization; |
4 | 5 | using System.Collections.Generic; |
@@ -199,6 +200,123 @@ public static void Unstage(IRepository repository, IEnumerable<string> paths, Ex |
199 | 200 | repository.Index.Replace(repository.Head.Tip, paths, explicitPathsOptions); |
200 | 201 | } |
201 | 202 | } |
| 203 | + |
| 204 | + /// <summary> |
| 205 | + /// Moves and/or renames a file in the working directory and promotes the change to the staging area. |
| 206 | + /// </summary> |
| 207 | + /// <param name="repository">The repository to act on</param> |
| 208 | + /// <param name="sourcePath">The path of the file within the working directory which has to be moved/renamed.</param> |
| 209 | + /// <param name="destinationPath">The target path of the file within the working directory.</param> |
| 210 | + public static void Move(IRepository repository, string sourcePath, string destinationPath) |
| 211 | + { |
| 212 | + Move(repository, new[] { sourcePath }, new[] { destinationPath }); |
| 213 | + } |
| 214 | + |
| 215 | + /// <summary> |
| 216 | + /// Moves and/or renames a collection of files in the working directory and promotes the changes to the staging area. |
| 217 | + /// </summary> |
| 218 | + /// <param name="repository">The repository to act on</param> |
| 219 | + /// <param name="sourcePaths">The paths of the files within the working directory which have to be moved/renamed.</param> |
| 220 | + /// <param name="destinationPaths">The target paths of the files within the working directory.</param> |
| 221 | + public static void Move(IRepository repository, IEnumerable<string> sourcePaths, IEnumerable<string> destinationPaths) |
| 222 | + { |
| 223 | + Ensure.ArgumentNotNull(repository, "repository"); |
| 224 | + Ensure.ArgumentNotNull(sourcePaths, "sourcePaths"); |
| 225 | + Ensure.ArgumentNotNull(destinationPaths, "destinationPaths"); |
| 226 | + |
| 227 | + //TODO: Move() should support following use cases: |
| 228 | + // - Moving a file under a directory ('file' and 'dir' -> 'dir/file') |
| 229 | + // - Moving a directory (and its content) under another directory ('dir1' and 'dir2' -> 'dir2/dir1/*') |
| 230 | + |
| 231 | + //TODO: Move() should throw when: |
| 232 | + // - Moving a directory under a file |
| 233 | + |
| 234 | + IDictionary<Tuple<string, FileStatus>, Tuple<string, FileStatus>> batch = PrepareBatch(repository, sourcePaths, destinationPaths); |
| 235 | + |
| 236 | + if (batch.Count == 0) |
| 237 | + { |
| 238 | + throw new ArgumentNullException("sourcePaths"); |
| 239 | + } |
| 240 | + |
| 241 | + foreach (KeyValuePair<Tuple<string, FileStatus>, Tuple<string, FileStatus>> keyValuePair in batch) |
| 242 | + { |
| 243 | + string sourcePath = keyValuePair.Key.Item1; |
| 244 | + string destPath = keyValuePair.Value.Item1; |
| 245 | + |
| 246 | + if (Directory.Exists(sourcePath) || Directory.Exists(destPath)) |
| 247 | + { |
| 248 | + throw new NotImplementedException(); |
| 249 | + } |
| 250 | + |
| 251 | + FileStatus sourceStatus = keyValuePair.Key.Item2; |
| 252 | + if (sourceStatus.HasAny(new Enum[] { FileStatus.Nonexistent, FileStatus.DeletedFromIndex, FileStatus.NewInWorkdir, FileStatus.DeletedFromWorkdir })) |
| 253 | + { |
| 254 | + throw new LibGit2SharpException("Unable to move file '{0}'. Its current status is '{1}'.", |
| 255 | + sourcePath, |
| 256 | + sourceStatus); |
| 257 | + } |
| 258 | + |
| 259 | + FileStatus desStatus = keyValuePair.Value.Item2; |
| 260 | + if (desStatus.HasAny(new Enum[] { FileStatus.Nonexistent, FileStatus.DeletedFromWorkdir })) |
| 261 | + { |
| 262 | + continue; |
| 263 | + } |
| 264 | + |
| 265 | + throw new LibGit2SharpException("Unable to overwrite file '{0}'. Its current status is '{1}'.", |
| 266 | + destPath, |
| 267 | + desStatus); |
| 268 | + } |
| 269 | + |
| 270 | + string wd = repository.Info.WorkingDirectory; |
| 271 | + var index = repository.Index; |
| 272 | + foreach (KeyValuePair<Tuple<string, FileStatus>, Tuple<string, FileStatus>> keyValuePair in batch) |
| 273 | + { |
| 274 | + string from = keyValuePair.Key.Item1; |
| 275 | + string to = keyValuePair.Value.Item1; |
| 276 | + |
| 277 | + index.Remove(from); |
| 278 | + File.Move(Path.Combine(wd, from), Path.Combine(wd, to)); |
| 279 | + index.Add(to); |
| 280 | + } |
| 281 | + |
| 282 | + index.Write(); |
| 283 | + } |
| 284 | + |
| 285 | + private static bool Enumerate(IEnumerator<string> leftEnum, IEnumerator<string> rightEnum) |
| 286 | + { |
| 287 | + bool isLeftEoF = leftEnum.MoveNext(); |
| 288 | + bool isRightEoF = rightEnum.MoveNext(); |
| 289 | + |
| 290 | + if (isLeftEoF == isRightEoF) |
| 291 | + { |
| 292 | + return isLeftEoF; |
| 293 | + } |
| 294 | + |
| 295 | + throw new ArgumentException("The collection of paths are of different lengths."); |
| 296 | + } |
| 297 | + |
| 298 | + private static IDictionary<Tuple<string, FileStatus>, Tuple<string, FileStatus>> PrepareBatch(IRepository repository, IEnumerable<string> leftPaths, IEnumerable<string> rightPaths) |
| 299 | + { |
| 300 | + IDictionary<Tuple<string, FileStatus>, Tuple<string, FileStatus>> dic = new Dictionary<Tuple<string, FileStatus>, Tuple<string, FileStatus>>(); |
| 301 | + |
| 302 | + IEnumerator<string> leftEnum = leftPaths.GetEnumerator(); |
| 303 | + IEnumerator<string> rightEnum = rightPaths.GetEnumerator(); |
| 304 | + |
| 305 | + while (Enumerate(leftEnum, rightEnum)) |
| 306 | + { |
| 307 | + Tuple<string, FileStatus> from = BuildFrom(repository, leftEnum.Current); |
| 308 | + Tuple<string, FileStatus> to = BuildFrom(repository, rightEnum.Current); |
| 309 | + dic.Add(from, to); |
| 310 | + } |
| 311 | + |
| 312 | + return dic; |
| 313 | + } |
| 314 | + |
| 315 | + private static Tuple<string, FileStatus> BuildFrom(IRepository repository, string path) |
| 316 | + { |
| 317 | + string relativePath = repository.BuildRelativePathFrom(path); |
| 318 | + return new Tuple<string, FileStatus>(relativePath, repository.RetrieveStatus(relativePath)); |
| 319 | + } |
202 | 320 | } |
203 | 321 | } |
204 | 322 |
|
0 commit comments