~ubuntu-branches/ubuntu/oneiric/monodevelop/oneiric

« back to all changes in this revision

Viewing changes to contrib/NGit/NGit.Transport/WalkPushConnection.cs

  • Committer: Bazaar Package Importer
  • Author(s): Jo Shields
  • Date: 2011-06-27 17:03:13 UTC
  • mto: (1.8.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 54.
  • Revision ID: james.westby@ubuntu.com-20110627170313-6cvz3s19x6e9hqe9
ImportĀ upstreamĀ versionĀ 2.5.92+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
This code is derived from jgit (http://eclipse.org/jgit).
 
3
Copyright owners are documented in jgit's IP log.
 
4
 
 
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
 
9
 
 
10
All rights reserved.
 
11
 
 
12
Redistribution and use in source and binary forms, with or
 
13
without modification, are permitted provided that the following
 
14
conditions are met:
 
15
 
 
16
- Redistributions of source code must retain the above copyright
 
17
  notice, this list of conditions and the following disclaimer.
 
18
 
 
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.
 
23
 
 
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
 
27
  written permission.
 
28
 
 
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.
 
42
*/
 
43
 
 
44
using System.Collections.Generic;
 
45
using System.IO;
 
46
using NGit;
 
47
using NGit.Errors;
 
48
using NGit.Storage.Pack;
 
49
using NGit.Transport;
 
50
using Sharpen;
 
51
 
 
52
namespace NGit.Transport
 
53
{
 
54
        /// <summary>Generic push support for dumb transport protocols.</summary>
 
55
        /// <remarks>
 
56
        /// Generic push support for dumb transport protocols.
 
57
        /// <p>
 
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.
 
63
        /// <p>
 
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.
 
67
        /// <p>
 
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.
 
71
        /// <p>
 
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.
 
76
        /// </remarks>
 
77
        /// <seealso cref="WalkRemoteObjectDatabase">WalkRemoteObjectDatabase</seealso>
 
78
        internal class WalkPushConnection : BaseConnection, PushConnection
 
79
        {
 
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;
 
83
 
 
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;
 
87
 
 
88
                /// <summary>Database connection to the remote repository.</summary>
 
89
                /// <remarks>Database connection to the remote repository.</remarks>
 
90
                private readonly WalkRemoteObjectDatabase dest;
 
91
 
 
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;
 
95
 
 
96
                /// <summary>Packs already known to reside in the remote repository.</summary>
 
97
                /// <remarks>
 
98
                /// Packs already known to reside in the remote repository.
 
99
                /// <p>
 
100
                /// This is a LinkedHashMap to maintain the original order.
 
101
                /// </remarks>
 
102
                private LinkedHashMap<string, string> packNames;
 
103
 
 
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;
 
107
 
 
108
                /// <summary>Updates which require altering the packed-refs file to complete.</summary>
 
109
                /// <remarks>
 
110
                /// Updates which require altering the packed-refs file to complete.
 
111
                /// <p>
 
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>
 
116
                /// will be written.
 
117
                /// </remarks>
 
118
                private ICollection<RemoteRefUpdate> packedRefUpdates;
 
119
 
 
120
                internal WalkPushConnection(WalkTransport walkTransport, WalkRemoteObjectDatabase
 
121
                         w)
 
122
                {
 
123
                        transport = (NGit.Transport.Transport)walkTransport;
 
124
                        local = transport.local;
 
125
                        uri = transport.GetURI();
 
126
                        dest = w;
 
127
                }
 
128
 
 
129
                /// <exception cref="NGit.Errors.TransportException"></exception>
 
130
                public virtual void Push(ProgressMonitor monitor, IDictionary<string, RemoteRefUpdate
 
131
                        > refUpdates)
 
132
                {
 
133
                        MarkStartedOperation();
 
134
                        packNames = null;
 
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.
 
140
                        //
 
141
                        IList<RemoteRefUpdate> updates = new AList<RemoteRefUpdate>();
 
142
                        foreach (RemoteRefUpdate u in refUpdates.Values)
 
143
                        {
 
144
                                string n = u.GetRemoteName();
 
145
                                if (!n.StartsWith("refs/") || !Repository.IsValidRefName(n))
 
146
                                {
 
147
                                        u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
 
148
                                        u.SetMessage(JGitText.Get().funnyRefname);
 
149
                                        continue;
 
150
                                }
 
151
                                if (AnyObjectId.Equals(ObjectId.ZeroId, u.GetNewObjectId()))
 
152
                                {
 
153
                                        DeleteCommand(u);
 
154
                                }
 
155
                                else
 
156
                                {
 
157
                                        updates.AddItem(u);
 
158
                                }
 
159
                        }
 
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.
 
163
                        //
 
164
                        if (!updates.IsEmpty())
 
165
                        {
 
166
                                Sendpack(updates, monitor);
 
167
                        }
 
168
                        foreach (RemoteRefUpdate u_1 in updates)
 
169
                        {
 
170
                                UpdateCommand(u_1);
 
171
                        }
 
172
                        // Is this a new repository? If so we should create additional
 
173
                        // metadata files so it is properly initialized during the push.
 
174
                        //
 
175
                        if (!updates.IsEmpty() && IsNewRepository())
 
176
                        {
 
177
                                CreateNewRepository(updates);
 
178
                        }
 
179
                        RefWriter refWriter = new _RefWriter_177(this, newRefs.Values);
 
180
                        if (!packedRefUpdates.IsEmpty())
 
181
                        {
 
182
                                try
 
183
                                {
 
184
                                        refWriter.WritePackedRefs();
 
185
                                        foreach (RemoteRefUpdate u_2 in packedRefUpdates)
 
186
                                        {
 
187
                                                u_2.SetStatus(RemoteRefUpdate.Status.OK);
 
188
                                        }
 
189
                                }
 
190
                                catch (IOException err)
 
191
                                {
 
192
                                        foreach (RemoteRefUpdate u_2 in packedRefUpdates)
 
193
                                        {
 
194
                                                u_2.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
 
195
                                                u_2.SetMessage(err.Message);
 
196
                                        }
 
197
                                        throw new TransportException(uri, JGitText.Get().failedUpdatingRefs, err);
 
198
                                }
 
199
                        }
 
200
                        try
 
201
                        {
 
202
                                refWriter.WriteInfoRefs();
 
203
                        }
 
204
                        catch (IOException err)
 
205
                        {
 
206
                                throw new TransportException(uri, JGitText.Get().failedUpdatingRefs, err);
 
207
                        }
 
208
                }
 
209
 
 
210
                private sealed class _RefWriter_177 : RefWriter
 
211
                {
 
212
                        public _RefWriter_177(WalkPushConnection _enclosing, ICollection<Ref> baseArg1) : 
 
213
                                base(baseArg1)
 
214
                        {
 
215
                                this._enclosing = _enclosing;
 
216
                        }
 
217
 
 
218
                        /// <exception cref="System.IO.IOException"></exception>
 
219
                        protected internal override void WriteFile(string file, byte[] content)
 
220
                        {
 
221
                                this._enclosing.dest.WriteFile(WalkRemoteObjectDatabase.ROOT_DIR + file, content);
 
222
                        }
 
223
 
 
224
                        private readonly WalkPushConnection _enclosing;
 
225
                }
 
226
 
 
227
                public override void Close()
 
228
                {
 
229
                        dest.Close();
 
230
                }
 
231
 
 
232
                /// <exception cref="NGit.Errors.TransportException"></exception>
 
233
                private void Sendpack(IList<RemoteRefUpdate> updates, ProgressMonitor monitor)
 
234
                {
 
235
                        string pathPack = null;
 
236
                        string pathIdx = null;
 
237
                        PackWriter writer = new PackWriter(transport.GetPackConfig(), local.NewObjectReader
 
238
                                ());
 
239
                        try
 
240
                        {
 
241
                                IList<ObjectId> need = new AList<ObjectId>();
 
242
                                IList<ObjectId> have = new AList<ObjectId>();
 
243
                                foreach (RemoteRefUpdate r in updates)
 
244
                                {
 
245
                                        need.AddItem(r.GetNewObjectId());
 
246
                                }
 
247
                                foreach (Ref r_1 in GetRefs())
 
248
                                {
 
249
                                        have.AddItem(r_1.GetObjectId());
 
250
                                        if (r_1.GetPeeledObjectId() != null)
 
251
                                        {
 
252
                                                have.AddItem(r_1.GetPeeledObjectId());
 
253
                                        }
 
254
                                }
 
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.
 
259
                                //
 
260
                                if (writer.GetObjectCount() == 0)
 
261
                                {
 
262
                                        return;
 
263
                                }
 
264
                                packNames = new LinkedHashMap<string, string>();
 
265
                                foreach (string n in dest.GetPackNames())
 
266
                                {
 
267
                                        packNames.Put(n, n);
 
268
                                }
 
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)
 
274
                                {
 
275
                                        // The remote already contains this pack. We should
 
276
                                        // remove the index before overwriting to prevent bad
 
277
                                        // offsets from appearing to clients.
 
278
                                        //
 
279
                                        dest.WriteInfoPacks(packNames.Keys);
 
280
                                        dest.DeleteFile(pathIdx);
 
281
                                }
 
282
                                // Write the pack file, then the index, as readers look the
 
283
                                // other direction (index, then pack file).
 
284
                                //
 
285
                                string wt = "Put " + Sharpen.Runtime.Substring(@base, 0, 12);
 
286
                                OutputStream os = dest.WriteFile(pathPack, monitor, wt + "..pack");
 
287
                                try
 
288
                                {
 
289
                                        os = new BufferedOutputStream(os);
 
290
                                        writer.WritePack(monitor, monitor, os);
 
291
                                }
 
292
                                finally
 
293
                                {
 
294
                                        os.Close();
 
295
                                }
 
296
                                os = dest.WriteFile(pathIdx, monitor, wt + "..idx");
 
297
                                try
 
298
                                {
 
299
                                        os = new BufferedOutputStream(os);
 
300
                                        writer.WriteIndex(os);
 
301
                                }
 
302
                                finally
 
303
                                {
 
304
                                        os.Close();
 
305
                                }
 
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.
 
309
                                //
 
310
                                AList<string> infoPacks = new AList<string>();
 
311
                                infoPacks.AddItem(packName);
 
312
                                Sharpen.Collections.AddAll(infoPacks, packNames.Keys);
 
313
                                dest.WriteInfoPacks(infoPacks);
 
314
                        }
 
315
                        catch (IOException err)
 
316
                        {
 
317
                                SafeDelete(pathIdx);
 
318
                                SafeDelete(pathPack);
 
319
                                throw new TransportException(uri, JGitText.Get().cannotStoreObjects, err);
 
320
                        }
 
321
                        finally
 
322
                        {
 
323
                                writer.Release();
 
324
                        }
 
325
                }
 
326
 
 
327
                private void SafeDelete(string path)
 
328
                {
 
329
                        if (path != null)
 
330
                        {
 
331
                                try
 
332
                                {
 
333
                                        dest.DeleteFile(path);
 
334
                                }
 
335
                                catch (IOException)
 
336
                                {
 
337
                                }
 
338
                        }
 
339
                }
 
340
 
 
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)
 
345
                {
 
346
                        Ref r = Sharpen.Collections.Remove(newRefs, u.GetRemoteName());
 
347
                        if (r == null)
 
348
                        {
 
349
                                // Already gone.
 
350
                                //
 
351
                                u.SetStatus(RemoteRefUpdate.Status.OK);
 
352
                                return;
 
353
                        }
 
354
                        if (r.GetStorage().IsPacked())
 
355
                        {
 
356
                                packedRefUpdates.AddItem(u);
 
357
                        }
 
358
                        if (r.GetStorage().IsLoose())
 
359
                        {
 
360
                                try
 
361
                                {
 
362
                                        dest.DeleteRef(u.GetRemoteName());
 
363
                                        u.SetStatus(RemoteRefUpdate.Status.OK);
 
364
                                }
 
365
                                catch (IOException e)
 
366
                                {
 
367
                                        u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
 
368
                                        u.SetMessage(e.Message);
 
369
                                }
 
370
                        }
 
371
                        try
 
372
                        {
 
373
                                dest.DeleteRefLog(u.GetRemoteName());
 
374
                        }
 
375
                        catch (IOException e)
 
376
                        {
 
377
                                u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
 
378
                                u.SetMessage(e.Message);
 
379
                        }
 
380
                }
 
381
 
 
382
                private void UpdateCommand(RemoteRefUpdate u)
 
383
                {
 
384
                        try
 
385
                        {
 
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);
 
390
                        }
 
391
                        catch (IOException e)
 
392
                        {
 
393
                                u.SetStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
 
394
                                u.SetMessage(e.Message);
 
395
                        }
 
396
                }
 
397
 
 
398
                private bool IsNewRepository()
 
399
                {
 
400
                        return GetRefsMap().IsEmpty() && packNames != null && packNames.IsEmpty();
 
401
                }
 
402
 
 
403
                /// <exception cref="NGit.Errors.TransportException"></exception>
 
404
                private void CreateNewRepository(IList<RemoteRefUpdate> updates)
 
405
                {
 
406
                        try
 
407
                        {
 
408
                                string @ref = "ref: " + PickHEAD(updates) + "\n";
 
409
                                byte[] bytes = Constants.Encode(@ref);
 
410
                                dest.WriteFile(WalkRemoteObjectDatabase.ROOT_DIR + Constants.HEAD, bytes);
 
411
                        }
 
412
                        catch (IOException e)
 
413
                        {
 
414
                                throw new TransportException(uri, JGitText.Get().cannotCreateHEAD, e);
 
415
                        }
 
416
                        try
 
417
                        {
 
418
                                string config = "[core]\n" + "\trepositoryformatversion = 0\n";
 
419
                                byte[] bytes = Constants.Encode(config);
 
420
                                dest.WriteFile(WalkRemoteObjectDatabase.ROOT_DIR + "config", bytes);
 
421
                        }
 
422
                        catch (IOException e)
 
423
                        {
 
424
                                throw new TransportException(uri, JGitText.Get().cannotCreateConfig, e);
 
425
                        }
 
426
                }
 
427
 
 
428
                private static string PickHEAD(IList<RemoteRefUpdate> updates)
 
429
                {
 
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.
 
433
                        //
 
434
                        foreach (RemoteRefUpdate u in updates)
 
435
                        {
 
436
                                string n = u.GetRemoteName();
 
437
                                if (n.Equals(Constants.R_HEADS + Constants.MASTER))
 
438
                                {
 
439
                                        return n;
 
440
                                }
 
441
                        }
 
442
                        // Pick any branch, under the assumption the user pushed only
 
443
                        // one to the remote side.
 
444
                        //
 
445
                        foreach (RemoteRefUpdate u_1 in updates)
 
446
                        {
 
447
                                string n = u_1.GetRemoteName();
 
448
                                if (n.StartsWith(Constants.R_HEADS))
 
449
                                {
 
450
                                        return n;
 
451
                                }
 
452
                        }
 
453
                        return updates[0].GetRemoteName();
 
454
                }
 
455
        }
 
456
}