PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/monodevelop-2.8.8.4/contrib/NGit/NGit.Transport/WalkPushConnection.cs

#
C# | 457 lines | 302 code | 21 blank | 134 comment | 24 complexity | 6e7c4a095fd4813d95ea3f377321ffd1 MD5 | raw file
Possible License(s): LGPL-2.1
  1. /*
  2. This code is derived from jgit (http://eclipse.org/jgit).
  3. Copyright owners are documented in jgit's IP log.
  4. This program and the accompanying materials are made available
  5. under the terms of the Eclipse Distribution License v1.0 which
  6. accompanies this distribution, is reproduced below, and is
  7. available at http://www.eclipse.org/org/documents/edl-v10.php
  8. All rights reserved.
  9. Redistribution and use in source and binary forms, with or
  10. without modification, are permitted provided that the following
  11. conditions are met:
  12. - Redistributions of source code must retain the above copyright
  13. notice, this list of conditions and the following disclaimer.
  14. - Redistributions in binary form must reproduce the above
  15. copyright notice, this list of conditions and the following
  16. disclaimer in the documentation and/or other materials provided
  17. with the distribution.
  18. - Neither the name of the Eclipse Foundation, Inc. nor the
  19. names of its contributors may be used to endorse or promote
  20. products derived from this software without specific prior
  21. written permission.
  22. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  23. CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  24. INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  25. OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  27. CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  28. SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  29. NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  30. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  31. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  32. STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  33. ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  34. ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  35. */
  36. using System.Collections.Generic;
  37. using System.IO;
  38. using NGit;
  39. using NGit.Errors;
  40. using NGit.Storage.Pack;
  41. using NGit.Transport;
  42. using NGit.Util.IO;
  43. using Sharpen;
  44. namespace NGit.Transport
  45. {
  46. /// <summary>Generic push support for dumb transport protocols.</summary>
  47. /// <remarks>
  48. /// Generic push support for dumb transport protocols.
  49. /// <p>
  50. /// Since there are no Git-specific smarts on the remote side of the connection
  51. /// the client side must handle everything on its own. The generic push support
  52. /// requires being able to delete, create and overwrite files on the remote side,
  53. /// as well as create any missing directories (if necessary). Typically this can
  54. /// be handled through an FTP style protocol.
  55. /// <p>
  56. /// Objects not on the remote side are uploaded as pack files, using one pack
  57. /// file per invocation. This simplifies the implementation as only two data
  58. /// files need to be written to the remote repository.
  59. /// <p>
  60. /// Push support supplied by this class is not multiuser safe. Concurrent pushes
  61. /// to the same repository may yield an inconsistent reference database which may
  62. /// confuse fetch clients.
  63. /// <p>
  64. /// A single push is concurrently safe with multiple fetch requests, due to the
  65. /// careful order of operations used to update the repository. Clients fetching
  66. /// may receive transient failures due to short reads on certain files if the
  67. /// protocol does not support atomic file replacement.
  68. /// </remarks>
  69. /// <seealso cref="WalkRemoteObjectDatabase">WalkRemoteObjectDatabase</seealso>
  70. internal class WalkPushConnection : BaseConnection, PushConnection
  71. {
  72. /// <summary>The repository this transport pushes out of.</summary>
  73. /// <remarks>The repository this transport pushes out of.</remarks>
  74. private readonly Repository local;
  75. /// <summary>Location of the remote repository we are writing to.</summary>
  76. /// <remarks>Location of the remote repository we are writing to.</remarks>
  77. private readonly URIish uri;
  78. /// <summary>Database connection to the remote repository.</summary>
  79. /// <remarks>Database connection to the remote repository.</remarks>
  80. private readonly WalkRemoteObjectDatabase dest;
  81. /// <summary>The configured transport we were constructed by.</summary>
  82. /// <remarks>The configured transport we were constructed by.</remarks>
  83. private readonly NGit.Transport.Transport transport;
  84. /// <summary>Packs already known to reside in the remote repository.</summary>
  85. /// <remarks>
  86. /// Packs already known to reside in the remote repository.
  87. /// <p>
  88. /// This is a LinkedHashMap to maintain the original order.
  89. /// </remarks>
  90. private LinkedHashMap<string, string> packNames;
  91. /// <summary>Complete listing of refs the remote will have after our push.</summary>
  92. /// <remarks>Complete listing of refs the remote will have after our push.</remarks>
  93. private IDictionary<string, Ref> newRefs;
  94. /// <summary>Updates which require altering the packed-refs file to complete.</summary>
  95. /// <remarks>
  96. /// Updates which require altering the packed-refs file to complete.
  97. /// <p>
  98. /// If this collection is non-empty then any refs listed in
  99. /// <see cref="newRefs">newRefs</see>
  100. /// with a storage class of
  101. /// <see cref="NGit.RefStorage.PACKED">NGit.RefStorage.PACKED</see>
  102. /// will be written.
  103. /// </remarks>
  104. private ICollection<RemoteRefUpdate> packedRefUpdates;
  105. internal WalkPushConnection(WalkTransport walkTransport, WalkRemoteObjectDatabase
  106. w)
  107. {
  108. transport = (NGit.Transport.Transport)walkTransport;
  109. local = transport.local;
  110. uri = transport.GetURI();
  111. dest = w;
  112. }
  113. /// <exception cref="NGit.Errors.TransportException"></exception>
  114. public virtual void Push(ProgressMonitor monitor, IDictionary<string, RemoteRefUpdate
  115. > refUpdates)
  116. {
  117. MarkStartedOperation();
  118. packNames = null;
  119. newRefs = new SortedDictionary<string, Ref>(GetRefsMap());
  120. packedRefUpdates = new AList<RemoteRefUpdate>(refUpdates.Count);
  121. // Filter the commands and issue all deletes first. This way we
  122. // can correctly handle a directory being cleared out and a new
  123. // ref using the directory name being created.
  124. //
  125. IList<RemoteRefUpdate> updates = new AList<RemoteRefUpdate>();
  126. foreach (RemoteRefUpdate u in refUpdates.Values)
  127. {
  128. string n = u.GetRemoteName();
  129. if (!n.StartsWith("refs/") || !Repository.IsValidRefName(n))
  130. {
  131. u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
  132. u.SetMessage(JGitText.Get().funnyRefname);
  133. continue;
  134. }
  135. if (AnyObjectId.Equals(ObjectId.ZeroId, u.GetNewObjectId()))
  136. {
  137. DeleteCommand(u);
  138. }
  139. else
  140. {
  141. updates.AddItem(u);
  142. }
  143. }
  144. // If we have any updates we need to upload the objects first, to
  145. // prevent creating refs pointing at non-existent data. Then we
  146. // can update the refs, and the info-refs file for dumb transports.
  147. //
  148. if (!updates.IsEmpty())
  149. {
  150. Sendpack(updates, monitor);
  151. }
  152. foreach (RemoteRefUpdate u_1 in updates)
  153. {
  154. UpdateCommand(u_1);
  155. }
  156. // Is this a new repository? If so we should create additional
  157. // metadata files so it is properly initialized during the push.
  158. //
  159. if (!updates.IsEmpty() && IsNewRepository())
  160. {
  161. CreateNewRepository(updates);
  162. }
  163. RefWriter refWriter = new _RefWriter_179(this, newRefs.Values);
  164. if (!packedRefUpdates.IsEmpty())
  165. {
  166. try
  167. {
  168. refWriter.WritePackedRefs();
  169. foreach (RemoteRefUpdate u_2 in packedRefUpdates)
  170. {
  171. u_2.SetStatus(RemoteRefUpdate.Status.OK);
  172. }
  173. }
  174. catch (IOException err)
  175. {
  176. foreach (RemoteRefUpdate u_2 in packedRefUpdates)
  177. {
  178. u_2.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
  179. u_2.SetMessage(err.Message);
  180. }
  181. throw new TransportException(uri, JGitText.Get().failedUpdatingRefs, err);
  182. }
  183. }
  184. try
  185. {
  186. refWriter.WriteInfoRefs();
  187. }
  188. catch (IOException err)
  189. {
  190. throw new TransportException(uri, JGitText.Get().failedUpdatingRefs, err);
  191. }
  192. }
  193. private sealed class _RefWriter_179 : RefWriter
  194. {
  195. public _RefWriter_179(WalkPushConnection _enclosing, ICollection<Ref> baseArg1) :
  196. base(baseArg1)
  197. {
  198. this._enclosing = _enclosing;
  199. }
  200. /// <exception cref="System.IO.IOException"></exception>
  201. protected internal override void WriteFile(string file, byte[] content)
  202. {
  203. this._enclosing.dest.WriteFile(WalkRemoteObjectDatabase.ROOT_DIR + file, content);
  204. }
  205. private readonly WalkPushConnection _enclosing;
  206. }
  207. public override void Close()
  208. {
  209. dest.Close();
  210. }
  211. /// <exception cref="NGit.Errors.TransportException"></exception>
  212. private void Sendpack(IList<RemoteRefUpdate> updates, ProgressMonitor monitor)
  213. {
  214. string pathPack = null;
  215. string pathIdx = null;
  216. PackWriter writer = new PackWriter(transport.GetPackConfig(), local.NewObjectReader
  217. ());
  218. try
  219. {
  220. ICollection<ObjectId> need = new HashSet<ObjectId>();
  221. ICollection<ObjectId> have = new HashSet<ObjectId>();
  222. foreach (RemoteRefUpdate r in updates)
  223. {
  224. need.AddItem(r.GetNewObjectId());
  225. }
  226. foreach (Ref r_1 in GetRefs())
  227. {
  228. have.AddItem(r_1.GetObjectId());
  229. if (r_1.GetPeeledObjectId() != null)
  230. {
  231. have.AddItem(r_1.GetPeeledObjectId());
  232. }
  233. }
  234. writer.PreparePack(monitor, need, have);
  235. // We don't have to continue further if the pack will
  236. // be an empty pack, as the remote has all objects it
  237. // needs to complete this change.
  238. //
  239. if (writer.GetObjectCount() == 0)
  240. {
  241. return;
  242. }
  243. packNames = new LinkedHashMap<string, string>();
  244. foreach (string n in dest.GetPackNames())
  245. {
  246. packNames.Put(n, n);
  247. }
  248. string @base = "pack-" + writer.ComputeName().Name;
  249. string packName = @base + ".pack";
  250. pathPack = "pack/" + packName;
  251. pathIdx = "pack/" + @base + ".idx";
  252. if (Sharpen.Collections.Remove(packNames, packName) != null)
  253. {
  254. // The remote already contains this pack. We should
  255. // remove the index before overwriting to prevent bad
  256. // offsets from appearing to clients.
  257. //
  258. dest.WriteInfoPacks(packNames.Keys);
  259. dest.DeleteFile(pathIdx);
  260. }
  261. // Write the pack file, then the index, as readers look the
  262. // other direction (index, then pack file).
  263. //
  264. string wt = "Put " + Sharpen.Runtime.Substring(@base, 0, 12);
  265. OutputStream os = dest.WriteFile(pathPack, monitor, wt + "..pack");
  266. try
  267. {
  268. os = new SafeBufferedOutputStream(os);
  269. writer.WritePack(monitor, monitor, os);
  270. }
  271. finally
  272. {
  273. os.Close();
  274. }
  275. os = dest.WriteFile(pathIdx, monitor, wt + "..idx");
  276. try
  277. {
  278. os = new SafeBufferedOutputStream(os);
  279. writer.WriteIndex(os);
  280. }
  281. finally
  282. {
  283. os.Close();
  284. }
  285. // Record the pack at the start of the pack info list. This
  286. // way clients are likely to consult the newest pack first,
  287. // and discover the most recent objects there.
  288. //
  289. AList<string> infoPacks = new AList<string>();
  290. infoPacks.AddItem(packName);
  291. Sharpen.Collections.AddAll(infoPacks, packNames.Keys);
  292. dest.WriteInfoPacks(infoPacks);
  293. }
  294. catch (IOException err)
  295. {
  296. SafeDelete(pathIdx);
  297. SafeDelete(pathPack);
  298. throw new TransportException(uri, JGitText.Get().cannotStoreObjects, err);
  299. }
  300. finally
  301. {
  302. writer.Release();
  303. }
  304. }
  305. private void SafeDelete(string path)
  306. {
  307. if (path != null)
  308. {
  309. try
  310. {
  311. dest.DeleteFile(path);
  312. }
  313. catch (IOException)
  314. {
  315. }
  316. }
  317. }
  318. // Ignore the deletion failure. We probably are
  319. // already failing and were just trying to pick
  320. // up after ourselves.
  321. private void DeleteCommand(RemoteRefUpdate u)
  322. {
  323. Ref r = Sharpen.Collections.Remove(newRefs, u.GetRemoteName());
  324. if (r == null)
  325. {
  326. // Already gone.
  327. //
  328. u.SetStatus(RemoteRefUpdate.Status.OK);
  329. return;
  330. }
  331. if (r.GetStorage().IsPacked())
  332. {
  333. packedRefUpdates.AddItem(u);
  334. }
  335. if (r.GetStorage().IsLoose())
  336. {
  337. try
  338. {
  339. dest.DeleteRef(u.GetRemoteName());
  340. u.SetStatus(RemoteRefUpdate.Status.OK);
  341. }
  342. catch (IOException e)
  343. {
  344. u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
  345. u.SetMessage(e.Message);
  346. }
  347. }
  348. try
  349. {
  350. dest.DeleteRefLog(u.GetRemoteName());
  351. }
  352. catch (IOException e)
  353. {
  354. u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
  355. u.SetMessage(e.Message);
  356. }
  357. }
  358. private void UpdateCommand(RemoteRefUpdate u)
  359. {
  360. try
  361. {
  362. dest.WriteRef(u.GetRemoteName(), u.GetNewObjectId());
  363. newRefs.Put(u.GetRemoteName(), new ObjectIdRef.Unpeeled(RefStorage.LOOSE, u.GetRemoteName
  364. (), u.GetNewObjectId()));
  365. u.SetStatus(RemoteRefUpdate.Status.OK);
  366. }
  367. catch (IOException e)
  368. {
  369. u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
  370. u.SetMessage(e.Message);
  371. }
  372. }
  373. private bool IsNewRepository()
  374. {
  375. return GetRefsMap().IsEmpty() && packNames != null && packNames.IsEmpty();
  376. }
  377. /// <exception cref="NGit.Errors.TransportException"></exception>
  378. private void CreateNewRepository(IList<RemoteRefUpdate> updates)
  379. {
  380. try
  381. {
  382. string @ref = "ref: " + PickHEAD(updates) + "\n";
  383. byte[] bytes = Constants.Encode(@ref);
  384. dest.WriteFile(WalkRemoteObjectDatabase.ROOT_DIR + Constants.HEAD, bytes);
  385. }
  386. catch (IOException e)
  387. {
  388. throw new TransportException(uri, JGitText.Get().cannotCreateHEAD, e);
  389. }
  390. try
  391. {
  392. string config = "[core]\n" + "\trepositoryformatversion = 0\n";
  393. byte[] bytes = Constants.Encode(config);
  394. dest.WriteFile(WalkRemoteObjectDatabase.ROOT_DIR + Constants.CONFIG, bytes);
  395. }
  396. catch (IOException e)
  397. {
  398. throw new TransportException(uri, JGitText.Get().cannotCreateConfig, e);
  399. }
  400. }
  401. private static string PickHEAD(IList<RemoteRefUpdate> updates)
  402. {
  403. // Try to use master if the user is pushing that, it is the
  404. // default branch and is likely what they want to remain as
  405. // the default on the new remote.
  406. //
  407. foreach (RemoteRefUpdate u in updates)
  408. {
  409. string n = u.GetRemoteName();
  410. if (n.Equals(Constants.R_HEADS + Constants.MASTER))
  411. {
  412. return n;
  413. }
  414. }
  415. // Pick any branch, under the assumption the user pushed only
  416. // one to the remote side.
  417. //
  418. foreach (RemoteRefUpdate u_1 in updates)
  419. {
  420. string n = u_1.GetRemoteName();
  421. if (n.StartsWith(Constants.R_HEADS))
  422. {
  423. return n;
  424. }
  425. }
  426. return updates[0].GetRemoteName();
  427. }
  428. }
  429. }