2
This code is derived from jgit (http://eclipse.org/jgit).
3
Copyright owners are documented in jgit's IP log.
5
This program and the accompanying materials are made available
6
under the terms of the Eclipse Distribution License v1.0 which
7
accompanies this distribution, is reproduced below, and is
8
available at http://www.eclipse.org/org/documents/edl-v10.php
12
Redistribution and use in source and binary forms, with or
13
without modification, are permitted provided that the following
16
- Redistributions of source code must retain the above copyright
17
notice, this list of conditions and the following disclaimer.
19
- Redistributions in binary form must reproduce the above
20
copyright notice, this list of conditions and the following
21
disclaimer in the documentation and/or other materials provided
22
with the distribution.
24
- Neither the name of the Eclipse Foundation, Inc. nor the
25
names of its contributors may be used to endorse or promote
26
products derived from this software without specific prior
29
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44
using System.Collections.Generic;
48
using NGit.Storage.Pack;
52
namespace NGit.Transport
54
/// <summary>Generic push support for dumb transport protocols.</summary>
56
/// Generic push support for dumb transport protocols.
58
/// Since there are no Git-specific smarts on the remote side of the connection
59
/// the client side must handle everything on its own. The generic push support
60
/// requires being able to delete, create and overwrite files on the remote side,
61
/// as well as create any missing directories (if necessary). Typically this can
62
/// be handled through an FTP style protocol.
64
/// Objects not on the remote side are uploaded as pack files, using one pack
65
/// file per invocation. This simplifies the implementation as only two data
66
/// files need to be written to the remote repository.
68
/// Push support supplied by this class is not multiuser safe. Concurrent pushes
69
/// to the same repository may yield an inconsistent reference database which may
70
/// confuse fetch clients.
72
/// A single push is concurrently safe with multiple fetch requests, due to the
73
/// careful order of operations used to update the repository. Clients fetching
74
/// may receive transient failures due to short reads on certain files if the
75
/// protocol does not support atomic file replacement.
77
/// <seealso cref="WalkRemoteObjectDatabase">WalkRemoteObjectDatabase</seealso>
78
internal class WalkPushConnection : BaseConnection, PushConnection
80
/// <summary>The repository this transport pushes out of.</summary>
81
/// <remarks>The repository this transport pushes out of.</remarks>
82
private readonly Repository local;
84
/// <summary>Location of the remote repository we are writing to.</summary>
85
/// <remarks>Location of the remote repository we are writing to.</remarks>
86
private readonly URIish uri;
88
/// <summary>Database connection to the remote repository.</summary>
89
/// <remarks>Database connection to the remote repository.</remarks>
90
private readonly WalkRemoteObjectDatabase dest;
92
/// <summary>The configured transport we were constructed by.</summary>
93
/// <remarks>The configured transport we were constructed by.</remarks>
94
private readonly NGit.Transport.Transport transport;
96
/// <summary>Packs already known to reside in the remote repository.</summary>
98
/// Packs already known to reside in the remote repository.
100
/// This is a LinkedHashMap to maintain the original order.
102
private LinkedHashMap<string, string> packNames;
104
/// <summary>Complete listing of refs the remote will have after our push.</summary>
105
/// <remarks>Complete listing of refs the remote will have after our push.</remarks>
106
private IDictionary<string, Ref> newRefs;
108
/// <summary>Updates which require altering the packed-refs file to complete.</summary>
110
/// Updates which require altering the packed-refs file to complete.
112
/// If this collection is non-empty then any refs listed in
113
/// <see cref="newRefs">newRefs</see>
114
/// with a storage class of
115
/// <see cref="NGit.RefStorage.PACKED">NGit.RefStorage.PACKED</see>
118
private ICollection<RemoteRefUpdate> packedRefUpdates;
120
internal WalkPushConnection(WalkTransport walkTransport, WalkRemoteObjectDatabase
123
transport = (NGit.Transport.Transport)walkTransport;
124
local = transport.local;
125
uri = transport.GetURI();
129
/// <exception cref="NGit.Errors.TransportException"></exception>
130
public virtual void Push(ProgressMonitor monitor, IDictionary<string, RemoteRefUpdate
133
MarkStartedOperation();
135
newRefs = new SortedDictionary<string, Ref>(GetRefsMap());
136
packedRefUpdates = new AList<RemoteRefUpdate>(refUpdates.Count);
137
// Filter the commands and issue all deletes first. This way we
138
// can correctly handle a directory being cleared out and a new
139
// ref using the directory name being created.
141
IList<RemoteRefUpdate> updates = new AList<RemoteRefUpdate>();
142
foreach (RemoteRefUpdate u in refUpdates.Values)
144
string n = u.GetRemoteName();
145
if (!n.StartsWith("refs/") || !Repository.IsValidRefName(n))
147
u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
148
u.SetMessage(JGitText.Get().funnyRefname);
151
if (AnyObjectId.Equals(ObjectId.ZeroId, u.GetNewObjectId()))
160
// If we have any updates we need to upload the objects first, to
161
// prevent creating refs pointing at non-existent data. Then we
162
// can update the refs, and the info-refs file for dumb transports.
164
if (!updates.IsEmpty())
166
Sendpack(updates, monitor);
168
foreach (RemoteRefUpdate u_1 in updates)
172
// Is this a new repository? If so we should create additional
173
// metadata files so it is properly initialized during the push.
175
if (!updates.IsEmpty() && IsNewRepository())
177
CreateNewRepository(updates);
179
RefWriter refWriter = new _RefWriter_177(this, newRefs.Values);
180
if (!packedRefUpdates.IsEmpty())
184
refWriter.WritePackedRefs();
185
foreach (RemoteRefUpdate u_2 in packedRefUpdates)
187
u_2.SetStatus(RemoteRefUpdate.Status.OK);
190
catch (IOException err)
192
foreach (RemoteRefUpdate u_2 in packedRefUpdates)
194
u_2.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
195
u_2.SetMessage(err.Message);
197
throw new TransportException(uri, JGitText.Get().failedUpdatingRefs, err);
202
refWriter.WriteInfoRefs();
204
catch (IOException err)
206
throw new TransportException(uri, JGitText.Get().failedUpdatingRefs, err);
210
private sealed class _RefWriter_177 : RefWriter
212
public _RefWriter_177(WalkPushConnection _enclosing, ICollection<Ref> baseArg1) :
215
this._enclosing = _enclosing;
218
/// <exception cref="System.IO.IOException"></exception>
219
protected internal override void WriteFile(string file, byte[] content)
221
this._enclosing.dest.WriteFile(WalkRemoteObjectDatabase.ROOT_DIR + file, content);
224
private readonly WalkPushConnection _enclosing;
227
public override void Close()
232
/// <exception cref="NGit.Errors.TransportException"></exception>
233
private void Sendpack(IList<RemoteRefUpdate> updates, ProgressMonitor monitor)
235
string pathPack = null;
236
string pathIdx = null;
237
PackWriter writer = new PackWriter(transport.GetPackConfig(), local.NewObjectReader
241
IList<ObjectId> need = new AList<ObjectId>();
242
IList<ObjectId> have = new AList<ObjectId>();
243
foreach (RemoteRefUpdate r in updates)
245
need.AddItem(r.GetNewObjectId());
247
foreach (Ref r_1 in GetRefs())
249
have.AddItem(r_1.GetObjectId());
250
if (r_1.GetPeeledObjectId() != null)
252
have.AddItem(r_1.GetPeeledObjectId());
255
writer.PreparePack(monitor, need, have);
256
// We don't have to continue further if the pack will
257
// be an empty pack, as the remote has all objects it
258
// needs to complete this change.
260
if (writer.GetObjectCount() == 0)
264
packNames = new LinkedHashMap<string, string>();
265
foreach (string n in dest.GetPackNames())
269
string @base = "pack-" + writer.ComputeName().Name;
270
string packName = @base + ".pack";
271
pathPack = "pack/" + packName;
272
pathIdx = "pack/" + @base + ".idx";
273
if (Sharpen.Collections.Remove(packNames, packName) != null)
275
// The remote already contains this pack. We should
276
// remove the index before overwriting to prevent bad
277
// offsets from appearing to clients.
279
dest.WriteInfoPacks(packNames.Keys);
280
dest.DeleteFile(pathIdx);
282
// Write the pack file, then the index, as readers look the
283
// other direction (index, then pack file).
285
string wt = "Put " + Sharpen.Runtime.Substring(@base, 0, 12);
286
OutputStream os = dest.WriteFile(pathPack, monitor, wt + "..pack");
289
os = new BufferedOutputStream(os);
290
writer.WritePack(monitor, monitor, os);
296
os = dest.WriteFile(pathIdx, monitor, wt + "..idx");
299
os = new BufferedOutputStream(os);
300
writer.WriteIndex(os);
306
// Record the pack at the start of the pack info list. This
307
// way clients are likely to consult the newest pack first,
308
// and discover the most recent objects there.
310
AList<string> infoPacks = new AList<string>();
311
infoPacks.AddItem(packName);
312
Sharpen.Collections.AddAll(infoPacks, packNames.Keys);
313
dest.WriteInfoPacks(infoPacks);
315
catch (IOException err)
318
SafeDelete(pathPack);
319
throw new TransportException(uri, JGitText.Get().cannotStoreObjects, err);
327
private void SafeDelete(string path)
333
dest.DeleteFile(path);
341
// Ignore the deletion failure. We probably are
342
// already failing and were just trying to pick
343
// up after ourselves.
344
private void DeleteCommand(RemoteRefUpdate u)
346
Ref r = Sharpen.Collections.Remove(newRefs, u.GetRemoteName());
351
u.SetStatus(RemoteRefUpdate.Status.OK);
354
if (r.GetStorage().IsPacked())
356
packedRefUpdates.AddItem(u);
358
if (r.GetStorage().IsLoose())
362
dest.DeleteRef(u.GetRemoteName());
363
u.SetStatus(RemoteRefUpdate.Status.OK);
365
catch (IOException e)
367
u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
368
u.SetMessage(e.Message);
373
dest.DeleteRefLog(u.GetRemoteName());
375
catch (IOException e)
377
u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
378
u.SetMessage(e.Message);
382
private void UpdateCommand(RemoteRefUpdate u)
386
dest.WriteRef(u.GetRemoteName(), u.GetNewObjectId());
387
newRefs.Put(u.GetRemoteName(), new ObjectIdRef.Unpeeled(RefStorage.LOOSE, u.GetRemoteName
388
(), u.GetNewObjectId()));
389
u.SetStatus(RemoteRefUpdate.Status.OK);
391
catch (IOException e)
393
u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
394
u.SetMessage(e.Message);
398
private bool IsNewRepository()
400
return GetRefsMap().IsEmpty() && packNames != null && packNames.IsEmpty();
403
/// <exception cref="NGit.Errors.TransportException"></exception>
404
private void CreateNewRepository(IList<RemoteRefUpdate> updates)
408
string @ref = "ref: " + PickHEAD(updates) + "\n";
409
byte[] bytes = Constants.Encode(@ref);
410
dest.WriteFile(WalkRemoteObjectDatabase.ROOT_DIR + Constants.HEAD, bytes);
412
catch (IOException e)
414
throw new TransportException(uri, JGitText.Get().cannotCreateHEAD, e);
418
string config = "[core]\n" + "\trepositoryformatversion = 0\n";
419
byte[] bytes = Constants.Encode(config);
420
dest.WriteFile(WalkRemoteObjectDatabase.ROOT_DIR + "config", bytes);
422
catch (IOException e)
424
throw new TransportException(uri, JGitText.Get().cannotCreateConfig, e);
428
private static string PickHEAD(IList<RemoteRefUpdate> updates)
430
// Try to use master if the user is pushing that, it is the
431
// default branch and is likely what they want to remain as
432
// the default on the new remote.
434
foreach (RemoteRefUpdate u in updates)
436
string n = u.GetRemoteName();
437
if (n.Equals(Constants.R_HEADS + Constants.MASTER))
442
// Pick any branch, under the assumption the user pushed only
443
// one to the remote side.
445
foreach (RemoteRefUpdate u_1 in updates)
447
string n = u_1.GetRemoteName();
448
if (n.StartsWith(Constants.R_HEADS))
453
return updates[0].GetRemoteName();