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

« back to all changes in this revision

Viewing changes to contrib/NGit/NGit.Api/RebaseCommand.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 System.Text;
 
47
using NGit;
 
48
using NGit.Api;
 
49
using NGit.Api.Errors;
 
50
using NGit.Diff;
 
51
using NGit.Dircache;
 
52
using NGit.Revwalk;
 
53
using NGit.Treewalk;
 
54
using NGit.Treewalk.Filter;
 
55
using NGit.Util;
 
56
using Sharpen;
 
57
 
 
58
namespace NGit.Api
 
59
{
 
60
        /// <summary>
 
61
        /// A class used to execute a
 
62
        /// <code>Rebase</code>
 
63
        /// command. It has setters for all
 
64
        /// supported options and arguments of this command and a
 
65
        /// <see cref="Call()">Call()</see>
 
66
        /// method
 
67
        /// to finally execute the command. Each instance of this class should only be
 
68
        /// used for one invocation of the command (means: one call to
 
69
        /// <see cref="Call()">Call()</see>
 
70
        /// )
 
71
        /// <p>
 
72
        /// </summary>
 
73
        /// <seealso><a
 
74
        /// *      href="http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html"
 
75
        /// *      >Git documentation about Rebase</a></seealso>
 
76
        public class RebaseCommand : GitCommand<RebaseResult>
 
77
        {
 
78
                /// <summary>The name of the "rebase-merge" folder</summary>
 
79
                public static readonly string REBASE_MERGE = "rebase-merge";
 
80
 
 
81
                /// <summary>The name of the "stopped-sha" file</summary>
 
82
                public static readonly string STOPPED_SHA = "stopped-sha";
 
83
 
 
84
                private static readonly string AUTHOR_SCRIPT = "author-script";
 
85
 
 
86
                private static readonly string DONE = "done";
 
87
 
 
88
                private static readonly string GIT_AUTHOR_DATE = "GIT_AUTHOR_DATE";
 
89
 
 
90
                private static readonly string GIT_AUTHOR_EMAIL = "GIT_AUTHOR_EMAIL";
 
91
 
 
92
                private static readonly string GIT_AUTHOR_NAME = "GIT_AUTHOR_NAME";
 
93
 
 
94
                private static readonly string GIT_REBASE_TODO = "git-rebase-todo";
 
95
 
 
96
                private static readonly string HEAD_NAME = "head-name";
 
97
 
 
98
                private static readonly string INTERACTIVE = "interactive";
 
99
 
 
100
                private static readonly string MESSAGE = "message";
 
101
 
 
102
                private static readonly string ONTO = "onto";
 
103
 
 
104
                private static readonly string PATCH = "patch";
 
105
 
 
106
                private static readonly string REBASE_HEAD = "head";
 
107
 
 
108
                /// <summary>The available operations</summary>
 
109
                public enum Operation
 
110
                {
 
111
                        BEGIN,
 
112
                        CONTINUE,
 
113
                        SKIP,
 
114
                        ABORT
 
115
                }
 
116
 
 
117
                private RebaseCommand.Operation operation = RebaseCommand.Operation.BEGIN;
 
118
 
 
119
                private RevCommit upstreamCommit;
 
120
 
 
121
                private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
 
122
 
 
123
                private readonly RevWalk walk;
 
124
 
 
125
                private readonly FilePath rebaseDir;
 
126
 
 
127
                /// <param name="repo"></param>
 
128
                protected internal RebaseCommand(Repository repo) : base(repo)
 
129
                {
 
130
                        walk = new RevWalk(repo);
 
131
                        rebaseDir = new FilePath(repo.Directory, REBASE_MERGE);
 
132
                }
 
133
 
 
134
                /// <summary>
 
135
                /// Executes the
 
136
                /// <code>Rebase</code>
 
137
                /// command with all the options and parameters
 
138
                /// collected by the setter methods of this class. Each instance of this
 
139
                /// class should only be used for one invocation of the command. Don't call
 
140
                /// this method twice on an instance.
 
141
                /// </summary>
 
142
                /// <returns>an object describing the result of this command</returns>
 
143
                /// <exception cref="NGit.Api.Errors.NoHeadException"></exception>
 
144
                /// <exception cref="NGit.Api.Errors.RefNotFoundException"></exception>
 
145
                /// <exception cref="NGit.Api.Errors.JGitInternalException"></exception>
 
146
                /// <exception cref="NGit.Api.Errors.GitAPIException"></exception>
 
147
                public override RebaseResult Call()
 
148
                {
 
149
                        RevCommit newHead = null;
 
150
                        bool lastStepWasForward = false;
 
151
                        CheckCallable();
 
152
                        CheckParameters();
 
153
                        try
 
154
                        {
 
155
                                switch (operation)
 
156
                                {
 
157
                                        case RebaseCommand.Operation.ABORT:
 
158
                                        {
 
159
                                                try
 
160
                                                {
 
161
                                                        return Abort(new RebaseResult(RebaseResult.Status.ABORTED));
 
162
                                                }
 
163
                                                catch (IOException ioe)
 
164
                                                {
 
165
                                                        throw new JGitInternalException(ioe.Message, ioe);
 
166
                                                }
 
167
                                                goto case RebaseCommand.Operation.SKIP;
 
168
                                        }
 
169
 
 
170
                                        case RebaseCommand.Operation.SKIP:
 
171
                                        case RebaseCommand.Operation.CONTINUE:
 
172
                                        {
 
173
                                                // fall through
 
174
                                                string upstreamCommitName = ReadFile(rebaseDir, ONTO);
 
175
                                                this.upstreamCommit = walk.ParseCommit(repo.Resolve(upstreamCommitName));
 
176
                                                break;
 
177
                                        }
 
178
 
 
179
                                        case RebaseCommand.Operation.BEGIN:
 
180
                                        {
 
181
                                                RebaseResult res = InitFilesAndRewind();
 
182
                                                if (res != null)
 
183
                                                {
 
184
                                                        return res;
 
185
                                                }
 
186
                                        break;
 
187
                                        }
 
188
                                }
 
189
                                if (monitor.IsCancelled())
 
190
                                {
 
191
                                        return Abort(new RebaseResult(RebaseResult.Status.ABORTED));
 
192
                                }
 
193
                                if (operation == RebaseCommand.Operation.CONTINUE)
 
194
                                {
 
195
                                        newHead = ContinueRebase();
 
196
                                }
 
197
                                if (operation == RebaseCommand.Operation.SKIP)
 
198
                                {
 
199
                                        newHead = CheckoutCurrentHead();
 
200
                                }
 
201
                                ObjectReader or = repo.NewObjectReader();
 
202
                                IList<RebaseCommand.Step> steps = LoadSteps();
 
203
                                foreach (RebaseCommand.Step step in steps)
 
204
                                {
 
205
                                        PopSteps(1);
 
206
                                        ICollection<ObjectId> ids = or.Resolve(step.commit);
 
207
                                        if (ids.Count != 1)
 
208
                                        {
 
209
                                                throw new JGitInternalException("Could not resolve uniquely the abbreviated object ID"
 
210
                                                        );
 
211
                                        }
 
212
                                        RevCommit commitToPick = walk.ParseCommit(ids.Iterator().Next());
 
213
                                        if (monitor.IsCancelled())
 
214
                                        {
 
215
                                                return new RebaseResult(commitToPick);
 
216
                                        }
 
217
                                        try
 
218
                                        {
 
219
                                                monitor.BeginTask(MessageFormat.Format(JGitText.Get().applyingCommit, commitToPick
 
220
                                                        .GetShortMessage()), ProgressMonitor.UNKNOWN);
 
221
                                                // if the first parent of commitToPick is the current HEAD,
 
222
                                                // we do a fast-forward instead of cherry-pick to avoid
 
223
                                                // unnecessary object rewriting
 
224
                                                newHead = TryFastForward(commitToPick);
 
225
                                                lastStepWasForward = newHead != null;
 
226
                                                if (!lastStepWasForward)
 
227
                                                {
 
228
                                                        // TODO if the content of this commit is already merged
 
229
                                                        // here we should skip this step in order to avoid
 
230
                                                        // confusing pseudo-changed
 
231
                                                        CherryPickResult cherryPickResult = new Git(repo).CherryPick().Include(commitToPick
 
232
                                                                ).Call();
 
233
                                                        switch (cherryPickResult.GetStatus())
 
234
                                                        {
 
235
                                                                case CherryPickResult.CherryPickStatus.FAILED:
 
236
                                                                {
 
237
                                                                        if (operation == RebaseCommand.Operation.BEGIN)
 
238
                                                                        {
 
239
                                                                                return Abort(new RebaseResult(cherryPickResult.GetFailingPaths()));
 
240
                                                                        }
 
241
                                                                        else
 
242
                                                                        {
 
243
                                                                                return Stop(commitToPick);
 
244
                                                                        }
 
245
                                                                        goto case CherryPickResult.CherryPickStatus.CONFLICTING;
 
246
                                                                }
 
247
 
 
248
                                                                case CherryPickResult.CherryPickStatus.CONFLICTING:
 
249
                                                                {
 
250
                                                                        return Stop(commitToPick);
 
251
                                                                }
 
252
 
 
253
                                                                case CherryPickResult.CherryPickStatus.OK:
 
254
                                                                {
 
255
                                                                        newHead = cherryPickResult.GetNewHead();
 
256
                                                                        break;
 
257
                                                                }
 
258
                                                        }
 
259
                                                }
 
260
                                        }
 
261
                                        finally
 
262
                                        {
 
263
                                                monitor.EndTask();
 
264
                                        }
 
265
                                }
 
266
                                if (newHead != null)
 
267
                                {
 
268
                                        // point the previous head (if any) to the new commit
 
269
                                        string headName = ReadFile(rebaseDir, HEAD_NAME);
 
270
                                        if (headName.StartsWith(Constants.R_REFS))
 
271
                                        {
 
272
                                                RefUpdate rup = repo.UpdateRef(headName);
 
273
                                                rup.SetNewObjectId(newHead);
 
274
                                                RefUpdate.Result res_1 = rup.ForceUpdate();
 
275
                                                switch (res_1)
 
276
                                                {
 
277
                                                        case RefUpdate.Result.FAST_FORWARD:
 
278
                                                        case RefUpdate.Result.FORCED:
 
279
                                                        case RefUpdate.Result.NO_CHANGE:
 
280
                                                        {
 
281
                                                                break;
 
282
                                                        }
 
283
 
 
284
                                                        default:
 
285
                                                        {
 
286
                                                                throw new JGitInternalException("Updating HEAD failed");
 
287
                                                        }
 
288
                                                }
 
289
                                                rup = repo.UpdateRef(Constants.HEAD);
 
290
                                                res_1 = rup.Link(headName);
 
291
                                                switch (res_1)
 
292
                                                {
 
293
                                                        case RefUpdate.Result.FAST_FORWARD:
 
294
                                                        case RefUpdate.Result.FORCED:
 
295
                                                        case RefUpdate.Result.NO_CHANGE:
 
296
                                                        {
 
297
                                                                break;
 
298
                                                        }
 
299
 
 
300
                                                        default:
 
301
                                                        {
 
302
                                                                throw new JGitInternalException("Updating HEAD failed");
 
303
                                                        }
 
304
                                                }
 
305
                                        }
 
306
                                        FileUtils.Delete(rebaseDir, FileUtils.RECURSIVE);
 
307
                                        if (lastStepWasForward)
 
308
                                        {
 
309
                                                return new RebaseResult(RebaseResult.Status.FAST_FORWARD);
 
310
                                        }
 
311
                                        return new RebaseResult(RebaseResult.Status.OK);
 
312
                                }
 
313
                                return new RebaseResult(RebaseResult.Status.UP_TO_DATE);
 
314
                        }
 
315
                        catch (IOException ioe)
 
316
                        {
 
317
                                throw new JGitInternalException(ioe.Message, ioe);
 
318
                        }
 
319
                }
 
320
 
 
321
                /// <exception cref="System.IO.IOException"></exception>
 
322
                /// <exception cref="NGit.Api.Errors.NoHeadException"></exception>
 
323
                /// <exception cref="NGit.Api.Errors.JGitInternalException"></exception>
 
324
                private RevCommit CheckoutCurrentHead()
 
325
                {
 
326
                        ObjectId headTree = repo.Resolve(Constants.HEAD + "^{tree}");
 
327
                        if (headTree == null)
 
328
                        {
 
329
                                throw new NoHeadException(JGitText.Get().cannotRebaseWithoutCurrentHead);
 
330
                        }
 
331
                        DirCache dc = repo.LockDirCache();
 
332
                        try
 
333
                        {
 
334
                                DirCacheCheckout dco = new DirCacheCheckout(repo, dc, headTree);
 
335
                                dco.SetFailOnConflict(false);
 
336
                                bool needsDeleteFiles = dco.Checkout();
 
337
                                if (needsDeleteFiles)
 
338
                                {
 
339
                                        IList<string> fileList = dco.GetToBeDeleted();
 
340
                                        foreach (string filePath in fileList)
 
341
                                        {
 
342
                                                FilePath fileToDelete = new FilePath(repo.WorkTree, filePath);
 
343
                                                if (fileToDelete.Exists())
 
344
                                                {
 
345
                                                        FileUtils.Delete(fileToDelete, FileUtils.RECURSIVE | FileUtils.RETRY);
 
346
                                                }
 
347
                                        }
 
348
                                }
 
349
                        }
 
350
                        finally
 
351
                        {
 
352
                                dc.Unlock();
 
353
                        }
 
354
                        RevWalk rw = new RevWalk(repo);
 
355
                        RevCommit commit = rw.ParseCommit(repo.Resolve(Constants.HEAD));
 
356
                        rw.Release();
 
357
                        return commit;
 
358
                }
 
359
 
 
360
                /// <returns>the commit if we had to do a commit, otherwise null</returns>
 
361
                /// <exception cref="NGit.Api.Errors.GitAPIException">NGit.Api.Errors.GitAPIException
 
362
                ///     </exception>
 
363
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
364
                private RevCommit ContinueRebase()
 
365
                {
 
366
                        // if there are still conflicts, we throw a specific Exception
 
367
                        DirCache dc = repo.ReadDirCache();
 
368
                        bool hasUnmergedPaths = dc.HasUnmergedPaths();
 
369
                        if (hasUnmergedPaths)
 
370
                        {
 
371
                                throw new UnmergedPathsException();
 
372
                        }
 
373
                        // determine whether we need to commit
 
374
                        TreeWalk treeWalk = new TreeWalk(repo);
 
375
                        treeWalk.Reset();
 
376
                        treeWalk.Recursive = true;
 
377
                        treeWalk.AddTree(new DirCacheIterator(dc));
 
378
                        ObjectId id = repo.Resolve(Constants.HEAD + "^{tree}");
 
379
                        if (id == null)
 
380
                        {
 
381
                                throw new NoHeadException(JGitText.Get().cannotRebaseWithoutCurrentHead);
 
382
                        }
 
383
                        treeWalk.AddTree(id);
 
384
                        treeWalk.Filter = TreeFilter.ANY_DIFF;
 
385
                        bool needsCommit = treeWalk.Next();
 
386
                        treeWalk.Release();
 
387
                        if (needsCommit)
 
388
                        {
 
389
                                CommitCommand commit = new Git(repo).Commit();
 
390
                                commit.SetMessage(ReadFile(rebaseDir, MESSAGE));
 
391
                                commit.SetAuthor(ParseAuthor());
 
392
                                return commit.Call();
 
393
                        }
 
394
                        return null;
 
395
                }
 
396
 
 
397
                /// <exception cref="System.IO.IOException"></exception>
 
398
                private PersonIdent ParseAuthor()
 
399
                {
 
400
                        FilePath authorScriptFile = new FilePath(rebaseDir, AUTHOR_SCRIPT);
 
401
                        byte[] raw;
 
402
                        try
 
403
                        {
 
404
                                raw = IOUtil.ReadFully(authorScriptFile);
 
405
                        }
 
406
                        catch (FileNotFoundException)
 
407
                        {
 
408
                                return null;
 
409
                        }
 
410
                        return ParseAuthor(raw);
 
411
                }
 
412
 
 
413
                /// <exception cref="System.IO.IOException"></exception>
 
414
                private RebaseResult Stop(RevCommit commitToPick)
 
415
                {
 
416
                        PersonIdent author = commitToPick.GetAuthorIdent();
 
417
                        string authorScript = ToAuthorScript(author);
 
418
                        CreateFile(rebaseDir, AUTHOR_SCRIPT, authorScript);
 
419
                        CreateFile(rebaseDir, MESSAGE, commitToPick.GetFullMessage());
 
420
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
 
421
                        DiffFormatter df = new DiffFormatter(bos);
 
422
                        df.SetRepository(repo);
 
423
                        df.Format(commitToPick.GetParent(0), commitToPick);
 
424
                        CreateFile(rebaseDir, PATCH, Sharpen.Runtime.GetStringForBytes(bos.ToByteArray(), 
 
425
                                Constants.CHARACTER_ENCODING));
 
426
                        CreateFile(rebaseDir, STOPPED_SHA, repo.NewObjectReader().Abbreviate(commitToPick
 
427
                                ).Name);
 
428
                        return new RebaseResult(commitToPick);
 
429
                }
 
430
 
 
431
                internal virtual string ToAuthorScript(PersonIdent author)
 
432
                {
 
433
                        StringBuilder sb = new StringBuilder(100);
 
434
                        sb.Append(GIT_AUTHOR_NAME);
 
435
                        sb.Append("='");
 
436
                        sb.Append(author.GetName());
 
437
                        sb.Append("'\n");
 
438
                        sb.Append(GIT_AUTHOR_EMAIL);
 
439
                        sb.Append("='");
 
440
                        sb.Append(author.GetEmailAddress());
 
441
                        sb.Append("'\n");
 
442
                        // the command line uses the "external String"
 
443
                        // representation for date and timezone
 
444
                        sb.Append(GIT_AUTHOR_DATE);
 
445
                        sb.Append("='");
 
446
                        string externalString = author.ToExternalString();
 
447
                        sb.Append(Sharpen.Runtime.Substring(externalString, externalString.LastIndexOf('>'
 
448
                                ) + 2));
 
449
                        sb.Append("'\n");
 
450
                        return sb.ToString();
 
451
                }
 
452
 
 
453
                /// <summary>
 
454
                /// Removes the number of lines given in the parameter from the
 
455
                /// <code>git-rebase-todo</code> file but preserves comments and other lines
 
456
                /// that can not be parsed as steps
 
457
                /// </summary>
 
458
                /// <param name="numSteps"></param>
 
459
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
460
                private void PopSteps(int numSteps)
 
461
                {
 
462
                        if (numSteps == 0)
 
463
                        {
 
464
                                return;
 
465
                        }
 
466
                        IList<string> todoLines = new AList<string>();
 
467
                        IList<string> poppedLines = new AList<string>();
 
468
                        FilePath todoFile = new FilePath(rebaseDir, GIT_REBASE_TODO);
 
469
                        FilePath doneFile = new FilePath(rebaseDir, DONE);
 
470
                        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(
 
471
                                todoFile), Constants.CHARACTER_ENCODING));
 
472
                        try
 
473
                        {
 
474
                                // check if the line starts with a action tag (pick, skip...)
 
475
                                while (poppedLines.Count < numSteps)
 
476
                                {
 
477
                                        string popCandidate = br.ReadLine();
 
478
                                        if (popCandidate == null)
 
479
                                        {
 
480
                                                break;
 
481
                                        }
 
482
                                        if (popCandidate[0] == '#')
 
483
                                        {
 
484
                                                continue;
 
485
                                        }
 
486
                                        int spaceIndex = popCandidate.IndexOf(' ');
 
487
                                        bool pop = false;
 
488
                                        if (spaceIndex >= 0)
 
489
                                        {
 
490
                                                string actionToken = Sharpen.Runtime.Substring(popCandidate, 0, spaceIndex);
 
491
                                                pop = RebaseCommand.Action.Parse(actionToken) != null;
 
492
                                        }
 
493
                                        if (pop)
 
494
                                        {
 
495
                                                poppedLines.AddItem(popCandidate);
 
496
                                        }
 
497
                                        else
 
498
                                        {
 
499
                                                todoLines.AddItem(popCandidate);
 
500
                                        }
 
501
                                }
 
502
                                string readLine = br.ReadLine();
 
503
                                while (readLine != null)
 
504
                                {
 
505
                                        todoLines.AddItem(readLine);
 
506
                                        readLine = br.ReadLine();
 
507
                                }
 
508
                        }
 
509
                        finally
 
510
                        {
 
511
                                br.Close();
 
512
                        }
 
513
                        BufferedWriter todoWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream
 
514
                                (todoFile), Constants.CHARACTER_ENCODING));
 
515
                        try
 
516
                        {
 
517
                                foreach (string writeLine in todoLines)
 
518
                                {
 
519
                                        todoWriter.Write(writeLine);
 
520
                                        todoWriter.NewLine();
 
521
                                }
 
522
                        }
 
523
                        finally
 
524
                        {
 
525
                                todoWriter.Close();
 
526
                        }
 
527
                        if (poppedLines.Count > 0)
 
528
                        {
 
529
                                // append here
 
530
                                BufferedWriter doneWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream
 
531
                                        (doneFile, true), Constants.CHARACTER_ENCODING));
 
532
                                try
 
533
                                {
 
534
                                        foreach (string writeLine in poppedLines)
 
535
                                        {
 
536
                                                doneWriter.Write(writeLine);
 
537
                                                doneWriter.NewLine();
 
538
                                        }
 
539
                                }
 
540
                                finally
 
541
                                {
 
542
                                        doneWriter.Close();
 
543
                                }
 
544
                        }
 
545
                }
 
546
 
 
547
                /// <exception cref="NGit.Api.Errors.RefNotFoundException"></exception>
 
548
                /// <exception cref="System.IO.IOException"></exception>
 
549
                /// <exception cref="NGit.Api.Errors.NoHeadException"></exception>
 
550
                /// <exception cref="NGit.Api.Errors.JGitInternalException"></exception>
 
551
                private RebaseResult InitFilesAndRewind()
 
552
                {
 
553
                        // we need to store everything into files so that we can implement
 
554
                        // --skip, --continue, and --abort
 
555
                        // first of all, we determine the commits to be applied
 
556
                        IList<RevCommit> cherryPickList = new AList<RevCommit>();
 
557
                        Ref head = repo.GetRef(Constants.HEAD);
 
558
                        if (head == null || head.GetObjectId() == null)
 
559
                        {
 
560
                                throw new RefNotFoundException(MessageFormat.Format(JGitText.Get().refNotResolved
 
561
                                        , Constants.HEAD));
 
562
                        }
 
563
                        string headName;
 
564
                        if (head.IsSymbolic())
 
565
                        {
 
566
                                headName = head.GetTarget().GetName();
 
567
                        }
 
568
                        else
 
569
                        {
 
570
                                headName = "detached HEAD";
 
571
                        }
 
572
                        ObjectId headId = head.GetObjectId();
 
573
                        if (headId == null)
 
574
                        {
 
575
                                throw new RefNotFoundException(MessageFormat.Format(JGitText.Get().refNotResolved
 
576
                                        , Constants.HEAD));
 
577
                        }
 
578
                        RevCommit headCommit = walk.LookupCommit(headId);
 
579
                        monitor.BeginTask(JGitText.Get().obtainingCommitsForCherryPick, ProgressMonitor.UNKNOWN
 
580
                                );
 
581
                        LogCommand cmd = new Git(repo).Log().AddRange(upstreamCommit, headCommit);
 
582
                        Iterable<RevCommit> commitsToUse = cmd.Call();
 
583
                        foreach (RevCommit commit in commitsToUse)
 
584
                        {
 
585
                                cherryPickList.AddItem(commit);
 
586
                        }
 
587
                        // if the upstream commit is in a direct line to the current head,
 
588
                        // the log command will not report any commits; in this case,
 
589
                        // we create the cherry-pick list ourselves
 
590
                        if (cherryPickList.IsEmpty())
 
591
                        {
 
592
                                Iterable<RevCommit> parents = new Git(repo).Log().Add(upstreamCommit).Call();
 
593
                                foreach (RevCommit parent in parents)
 
594
                                {
 
595
                                        if (parent.Equals(headCommit))
 
596
                                        {
 
597
                                                break;
 
598
                                        }
 
599
                                        if (parent.ParentCount != 1)
 
600
                                        {
 
601
                                                throw new JGitInternalException(JGitText.Get().canOnlyCherryPickCommitsWithOneParent
 
602
                                                        );
 
603
                                        }
 
604
                                        cherryPickList.AddItem(parent);
 
605
                                }
 
606
                        }
 
607
                        // nothing to do: return with UP_TO_DATE_RESULT
 
608
                        if (cherryPickList.IsEmpty())
 
609
                        {
 
610
                                return RebaseResult.UP_TO_DATE_RESULT;
 
611
                        }
 
612
                        Sharpen.Collections.Reverse(cherryPickList);
 
613
                        // create the folder for the meta information
 
614
                        FileUtils.Mkdir(rebaseDir);
 
615
                        CreateFile(repo.Directory, Constants.ORIG_HEAD, headId.Name);
 
616
                        CreateFile(rebaseDir, REBASE_HEAD, headId.Name);
 
617
                        CreateFile(rebaseDir, HEAD_NAME, headName);
 
618
                        CreateFile(rebaseDir, ONTO, upstreamCommit.Name);
 
619
                        CreateFile(rebaseDir, INTERACTIVE, string.Empty);
 
620
                        BufferedWriter fw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream
 
621
                                (new FilePath(rebaseDir, GIT_REBASE_TODO)), Constants.CHARACTER_ENCODING));
 
622
                        fw.Write("# Created by EGit: rebasing " + upstreamCommit.Name + " onto " + headId
 
623
                                .Name);
 
624
                        fw.NewLine();
 
625
                        try
 
626
                        {
 
627
                                StringBuilder sb = new StringBuilder();
 
628
                                ObjectReader reader = walk.GetObjectReader();
 
629
                                foreach (RevCommit commit_1 in cherryPickList)
 
630
                                {
 
631
                                        sb.Length = 0;
 
632
                                        sb.Append(RebaseCommand.Action.PICK.ToToken());
 
633
                                        sb.Append(" ");
 
634
                                        sb.Append(reader.Abbreviate(commit_1).Name);
 
635
                                        sb.Append(" ");
 
636
                                        sb.Append(commit_1.GetShortMessage());
 
637
                                        fw.Write(sb.ToString());
 
638
                                        fw.NewLine();
 
639
                                }
 
640
                        }
 
641
                        finally
 
642
                        {
 
643
                                fw.Close();
 
644
                        }
 
645
                        monitor.EndTask();
 
646
                        // we rewind to the upstream commit
 
647
                        monitor.BeginTask(MessageFormat.Format(JGitText.Get().rewinding, upstreamCommit.GetShortMessage
 
648
                                ()), ProgressMonitor.UNKNOWN);
 
649
                        CheckoutCommit(upstreamCommit);
 
650
                        monitor.EndTask();
 
651
                        return null;
 
652
                }
 
653
 
 
654
                /// <summary>checks if we can fast-forward and returns the new head if it is possible
 
655
                ///     </summary>
 
656
                /// <param name="newCommit"></param>
 
657
                /// <returns>the new head, or null</returns>
 
658
                /// <exception cref="NGit.Api.Errors.RefNotFoundException">NGit.Api.Errors.RefNotFoundException
 
659
                ///     </exception>
 
660
                /// <exception cref="System.IO.IOException">System.IO.IOException</exception>
 
661
                public virtual RevCommit TryFastForward(RevCommit newCommit)
 
662
                {
 
663
                        Ref head = repo.GetRef(Constants.HEAD);
 
664
                        if (head == null || head.GetObjectId() == null)
 
665
                        {
 
666
                                throw new RefNotFoundException(MessageFormat.Format(JGitText.Get().refNotResolved
 
667
                                        , Constants.HEAD));
 
668
                        }
 
669
                        ObjectId headId = head.GetObjectId();
 
670
                        if (headId == null)
 
671
                        {
 
672
                                throw new RefNotFoundException(MessageFormat.Format(JGitText.Get().refNotResolved
 
673
                                        , Constants.HEAD));
 
674
                        }
 
675
                        RevCommit headCommit = walk.LookupCommit(headId);
 
676
                        if (walk.IsMergedInto(newCommit, headCommit))
 
677
                        {
 
678
                                return newCommit;
 
679
                        }
 
680
                        string headName;
 
681
                        if (head.IsSymbolic())
 
682
                        {
 
683
                                headName = head.GetTarget().GetName();
 
684
                        }
 
685
                        else
 
686
                        {
 
687
                                headName = "detached HEAD";
 
688
                        }
 
689
                        return TryFastForward(headName, headCommit, newCommit);
 
690
                }
 
691
 
 
692
                /// <exception cref="System.IO.IOException"></exception>
 
693
                /// <exception cref="NGit.Api.Errors.JGitInternalException"></exception>
 
694
                private RevCommit TryFastForward(string headName, RevCommit oldCommit, RevCommit 
 
695
                        newCommit)
 
696
                {
 
697
                        bool tryRebase = false;
 
698
                        foreach (RevCommit parentCommit in newCommit.Parents)
 
699
                        {
 
700
                                if (parentCommit.Equals(oldCommit))
 
701
                                {
 
702
                                        tryRebase = true;
 
703
                                }
 
704
                        }
 
705
                        if (!tryRebase)
 
706
                        {
 
707
                                return null;
 
708
                        }
 
709
                        CheckoutCommand co = new CheckoutCommand(repo);
 
710
                        try
 
711
                        {
 
712
                                co.SetName(newCommit.Name).Call();
 
713
                                if (headName.StartsWith(Constants.R_HEADS))
 
714
                                {
 
715
                                        RefUpdate rup = repo.UpdateRef(headName);
 
716
                                        rup.SetExpectedOldObjectId(oldCommit);
 
717
                                        rup.SetNewObjectId(newCommit);
 
718
                                        rup.SetRefLogMessage("Fast-foward from " + oldCommit.Name + " to " + newCommit.Name
 
719
                                                , false);
 
720
                                        RefUpdate.Result res = rup.Update(walk);
 
721
                                        switch (res)
 
722
                                        {
 
723
                                                case RefUpdate.Result.FAST_FORWARD:
 
724
                                                case RefUpdate.Result.NO_CHANGE:
 
725
                                                case RefUpdate.Result.FORCED:
 
726
                                                {
 
727
                                                        break;
 
728
                                                }
 
729
 
 
730
                                                default:
 
731
                                                {
 
732
                                                        throw new IOException("Could not fast-forward");
 
733
                                                }
 
734
                                        }
 
735
                                }
 
736
                                return newCommit;
 
737
                        }
 
738
                        catch (RefAlreadyExistsException e)
 
739
                        {
 
740
                                throw new JGitInternalException(e.Message, e);
 
741
                        }
 
742
                        catch (RefNotFoundException e)
 
743
                        {
 
744
                                throw new JGitInternalException(e.Message, e);
 
745
                        }
 
746
                        catch (InvalidRefNameException e)
 
747
                        {
 
748
                                throw new JGitInternalException(e.Message, e);
 
749
                        }
 
750
                }
 
751
 
 
752
                /// <exception cref="NGit.Api.Errors.WrongRepositoryStateException"></exception>
 
753
                private void CheckParameters()
 
754
                {
 
755
                        RepositoryState s = repo.GetRepositoryState();
 
756
                        if (this.operation != RebaseCommand.Operation.BEGIN)
 
757
                        {
 
758
                                // these operations are only possible while in a rebasing state
 
759
                                if (repo.GetRepositoryState() != RepositoryState.REBASING_INTERACTIVE)
 
760
                                {
 
761
                                        throw new WrongRepositoryStateException(MessageFormat.Format(JGitText.Get().wrongRepositoryState
 
762
                                                , repo.GetRepositoryState().Name()));
 
763
                                }
 
764
                        }
 
765
                        else
 
766
                        {
 
767
                                if (s == RepositoryState.SAFE)
 
768
                                {
 
769
                                        if (this.upstreamCommit == null)
 
770
                                        {
 
771
                                                throw new JGitInternalException(MessageFormat.Format(JGitText.Get().missingRequiredParameter
 
772
                                                        , "upstream"));
 
773
                                        }
 
774
                                        return;
 
775
                                }
 
776
                                else
 
777
                                {
 
778
                                        throw new WrongRepositoryStateException(MessageFormat.Format(JGitText.Get().wrongRepositoryState
 
779
                                                , repo.GetRepositoryState().Name()));
 
780
                                }
 
781
                        }
 
782
                }
 
783
 
 
784
                /// <exception cref="System.IO.IOException"></exception>
 
785
                private void CreateFile(FilePath parentDir, string name, string content)
 
786
                {
 
787
                        FilePath file = new FilePath(parentDir, name);
 
788
                        FileOutputStream fos = new FileOutputStream(file);
 
789
                        try
 
790
                        {
 
791
                                fos.Write(Sharpen.Runtime.GetBytesForString(content, Constants.CHARACTER_ENCODING
 
792
                                        ));
 
793
                                fos.Write('\n');
 
794
                        }
 
795
                        finally
 
796
                        {
 
797
                                fos.Close();
 
798
                        }
 
799
                }
 
800
 
 
801
                /// <exception cref="System.IO.IOException"></exception>
 
802
                private RebaseResult Abort(RebaseResult result)
 
803
                {
 
804
                        try
 
805
                        {
 
806
                                string commitId = ReadFile(repo.Directory, Constants.ORIG_HEAD);
 
807
                                monitor.BeginTask(MessageFormat.Format(JGitText.Get().abortingRebase, commitId), 
 
808
                                        ProgressMonitor.UNKNOWN);
 
809
                                DirCacheCheckout dco;
 
810
                                RevCommit commit = walk.ParseCommit(repo.Resolve(commitId));
 
811
                                if (result.GetStatus().Equals(RebaseResult.Status.FAILED))
 
812
                                {
 
813
                                        RevCommit head = walk.ParseCommit(repo.Resolve(Constants.HEAD));
 
814
                                        dco = new DirCacheCheckout(repo, head.Tree, repo.LockDirCache(), commit.Tree);
 
815
                                }
 
816
                                else
 
817
                                {
 
818
                                        dco = new DirCacheCheckout(repo, repo.LockDirCache(), commit.Tree);
 
819
                                }
 
820
                                dco.SetFailOnConflict(false);
 
821
                                dco.Checkout();
 
822
                                walk.Release();
 
823
                        }
 
824
                        finally
 
825
                        {
 
826
                                monitor.EndTask();
 
827
                        }
 
828
                        try
 
829
                        {
 
830
                                string headName = ReadFile(rebaseDir, HEAD_NAME);
 
831
                                if (headName.StartsWith(Constants.R_REFS))
 
832
                                {
 
833
                                        monitor.BeginTask(MessageFormat.Format(JGitText.Get().resettingHead, headName), ProgressMonitor
 
834
                                                .UNKNOWN);
 
835
                                        // update the HEAD
 
836
                                        RefUpdate refUpdate = repo.UpdateRef(Constants.HEAD, false);
 
837
                                        RefUpdate.Result res = refUpdate.Link(headName);
 
838
                                        switch (res)
 
839
                                        {
 
840
                                                case RefUpdate.Result.FAST_FORWARD:
 
841
                                                case RefUpdate.Result.FORCED:
 
842
                                                case RefUpdate.Result.NO_CHANGE:
 
843
                                                {
 
844
                                                        break;
 
845
                                                }
 
846
 
 
847
                                                default:
 
848
                                                {
 
849
                                                        throw new JGitInternalException(JGitText.Get().abortingRebaseFailed);
 
850
                                                }
 
851
                                        }
 
852
                                }
 
853
                                // cleanup the files
 
854
                                FileUtils.Delete(rebaseDir, FileUtils.RECURSIVE);
 
855
                                return result;
 
856
                        }
 
857
                        finally
 
858
                        {
 
859
                                monitor.EndTask();
 
860
                        }
 
861
                }
 
862
 
 
863
                /// <exception cref="System.IO.IOException"></exception>
 
864
                private string ReadFile(FilePath directory, string fileName)
 
865
                {
 
866
                        byte[] content = IOUtil.ReadFully(new FilePath(directory, fileName));
 
867
                        // strip off the last LF
 
868
                        int end = content.Length;
 
869
                        while (0 < end && content[end - 1] == '\n')
 
870
                        {
 
871
                                end--;
 
872
                        }
 
873
                        return RawParseUtils.Decode(content, 0, end);
 
874
                }
 
875
 
 
876
                /// <exception cref="System.IO.IOException"></exception>
 
877
                private void CheckoutCommit(RevCommit commit)
 
878
                {
 
879
                        try
 
880
                        {
 
881
                                RevCommit head = walk.ParseCommit(repo.Resolve(Constants.HEAD));
 
882
                                DirCacheCheckout dco = new DirCacheCheckout(repo, head.Tree, repo.LockDirCache(), 
 
883
                                        commit.Tree);
 
884
                                dco.SetFailOnConflict(true);
 
885
                                dco.Checkout();
 
886
                                // update the HEAD
 
887
                                RefUpdate refUpdate = repo.UpdateRef(Constants.HEAD, true);
 
888
                                refUpdate.SetExpectedOldObjectId(head);
 
889
                                refUpdate.SetNewObjectId(commit);
 
890
                                RefUpdate.Result res = refUpdate.ForceUpdate();
 
891
                                switch (res)
 
892
                                {
 
893
                                        case RefUpdate.Result.FAST_FORWARD:
 
894
                                        case RefUpdate.Result.NO_CHANGE:
 
895
                                        case RefUpdate.Result.FORCED:
 
896
                                        {
 
897
                                                break;
 
898
                                        }
 
899
 
 
900
                                        default:
 
901
                                        {
 
902
                                                throw new IOException("Could not rewind to upstream commit");
 
903
                                        }
 
904
                                }
 
905
                        }
 
906
                        finally
 
907
                        {
 
908
                                walk.Release();
 
909
                                monitor.EndTask();
 
910
                        }
 
911
                }
 
912
 
 
913
                /// <exception cref="System.IO.IOException"></exception>
 
914
                private IList<RebaseCommand.Step> LoadSteps()
 
915
                {
 
916
                        byte[] buf = IOUtil.ReadFully(new FilePath(rebaseDir, GIT_REBASE_TODO));
 
917
                        int ptr = 0;
 
918
                        int tokenBegin = 0;
 
919
                        AList<RebaseCommand.Step> r = new AList<RebaseCommand.Step>();
 
920
                        while (ptr < buf.Length)
 
921
                        {
 
922
                                tokenBegin = ptr;
 
923
                                ptr = RawParseUtils.NextLF(buf, ptr);
 
924
                                int nextSpace = 0;
 
925
                                int tokenCount = 0;
 
926
                                RebaseCommand.Step current = null;
 
927
                                while (tokenCount < 3 && nextSpace < ptr)
 
928
                                {
 
929
                                        switch (tokenCount)
 
930
                                        {
 
931
                                                case 0:
 
932
                                                {
 
933
                                                        nextSpace = RawParseUtils.Next(buf, tokenBegin, ' ');
 
934
                                                        string actionToken = Sharpen.Runtime.GetStringForBytes(buf, tokenBegin, nextSpace
 
935
                                                                 - tokenBegin - 1);
 
936
                                                        tokenBegin = nextSpace;
 
937
                                                        if (actionToken[0] == '#')
 
938
                                                        {
 
939
                                                                tokenCount = 3;
 
940
                                                                break;
 
941
                                                        }
 
942
                                                        RebaseCommand.Action action = RebaseCommand.Action.Parse(actionToken);
 
943
                                                        if (action != null)
 
944
                                                        {
 
945
                                                                current = new RebaseCommand.Step(RebaseCommand.Action.Parse(actionToken));
 
946
                                                        }
 
947
                                                        break;
 
948
                                                }
 
949
 
 
950
                                                case 1:
 
951
                                                {
 
952
                                                        if (current == null)
 
953
                                                        {
 
954
                                                                break;
 
955
                                                        }
 
956
                                                        nextSpace = RawParseUtils.Next(buf, tokenBegin, ' ');
 
957
                                                        string commitToken = Sharpen.Runtime.GetStringForBytes(buf, tokenBegin, nextSpace
 
958
                                                                 - tokenBegin - 1);
 
959
                                                        tokenBegin = nextSpace;
 
960
                                                        current.commit = AbbreviatedObjectId.FromString(commitToken);
 
961
                                                        break;
 
962
                                                }
 
963
 
 
964
                                                case 2:
 
965
                                                {
 
966
                                                        if (current == null)
 
967
                                                        {
 
968
                                                                break;
 
969
                                                        }
 
970
                                                        nextSpace = ptr;
 
971
                                                        int length = ptr - tokenBegin;
 
972
                                                        current.shortMessage = new byte[length];
 
973
                                                        System.Array.Copy(buf, tokenBegin, current.shortMessage, 0, length);
 
974
                                                        r.AddItem(current);
 
975
                                                        break;
 
976
                                                }
 
977
                                        }
 
978
                                        tokenCount++;
 
979
                                }
 
980
                        }
 
981
                        return r;
 
982
                }
 
983
 
 
984
                /// <param name="upstream">the upstream commit</param>
 
985
                /// <returns>
 
986
                /// 
 
987
                /// <code>this</code>
 
988
                /// </returns>
 
989
                public virtual NGit.Api.RebaseCommand SetUpstream(RevCommit upstream)
 
990
                {
 
991
                        this.upstreamCommit = upstream;
 
992
                        return this;
 
993
                }
 
994
 
 
995
                /// <param name="upstream">id of the upstream commit</param>
 
996
                /// <returns>
 
997
                /// 
 
998
                /// <code>this</code>
 
999
                /// </returns>
 
1000
                public virtual NGit.Api.RebaseCommand SetUpstream(AnyObjectId upstream)
 
1001
                {
 
1002
                        try
 
1003
                        {
 
1004
                                this.upstreamCommit = walk.ParseCommit(upstream);
 
1005
                        }
 
1006
                        catch (IOException e)
 
1007
                        {
 
1008
                                throw new JGitInternalException(MessageFormat.Format(JGitText.Get().couldNotReadObjectWhileParsingCommit
 
1009
                                        , upstream.Name), e);
 
1010
                        }
 
1011
                        return this;
 
1012
                }
 
1013
 
 
1014
                /// <param name="upstream">the upstream branch</param>
 
1015
                /// <returns>
 
1016
                /// 
 
1017
                /// <code>this</code>
 
1018
                /// </returns>
 
1019
                /// <exception cref="NGit.Api.Errors.RefNotFoundException">NGit.Api.Errors.RefNotFoundException
 
1020
                ///     </exception>
 
1021
                public virtual NGit.Api.RebaseCommand SetUpstream(string upstream)
 
1022
                {
 
1023
                        try
 
1024
                        {
 
1025
                                ObjectId upstreamId = repo.Resolve(upstream);
 
1026
                                if (upstreamId == null)
 
1027
                                {
 
1028
                                        throw new RefNotFoundException(MessageFormat.Format(JGitText.Get().refNotResolved
 
1029
                                                , upstream));
 
1030
                                }
 
1031
                                upstreamCommit = walk.ParseCommit(repo.Resolve(upstream));
 
1032
                                return this;
 
1033
                        }
 
1034
                        catch (IOException ioe)
 
1035
                        {
 
1036
                                throw new JGitInternalException(ioe.Message, ioe);
 
1037
                        }
 
1038
                }
 
1039
 
 
1040
                /// <param name="operation">the operation to perform</param>
 
1041
                /// <returns>
 
1042
                /// 
 
1043
                /// <code>this</code>
 
1044
                /// </returns>
 
1045
                public virtual NGit.Api.RebaseCommand SetOperation(RebaseCommand.Operation operation
 
1046
                        )
 
1047
                {
 
1048
                        this.operation = operation;
 
1049
                        return this;
 
1050
                }
 
1051
 
 
1052
                /// <param name="monitor">a progress monitor</param>
 
1053
                /// <returns>this instance</returns>
 
1054
                public virtual NGit.Api.RebaseCommand SetProgressMonitor(ProgressMonitor monitor)
 
1055
                {
 
1056
                        this.monitor = monitor;
 
1057
                        return this;
 
1058
                }
 
1059
 
 
1060
                internal class Action
 
1061
                {
 
1062
                        public static RebaseCommand.Action PICK = new RebaseCommand.Action("pick");
 
1063
 
 
1064
                        public readonly string token;
 
1065
 
 
1066
                        private Action(string token)
 
1067
                        {
 
1068
                                // later add SQUASH, EDIT, etc.
 
1069
                                this.token = token;
 
1070
                        }
 
1071
 
 
1072
                        public virtual string ToToken()
 
1073
                        {
 
1074
                                return this.token;
 
1075
                        }
 
1076
 
 
1077
                        internal static RebaseCommand.Action Parse(string token)
 
1078
                        {
 
1079
                                if (token.Equals("pick") || token.Equals("p"))
 
1080
                                {
 
1081
                                        return PICK;
 
1082
                                }
 
1083
                                throw new JGitInternalException(MessageFormat.Format("Unknown or unsupported command \"{0}\", only  \"pick\" is allowed"
 
1084
                                        , token));
 
1085
                        }
 
1086
                }
 
1087
 
 
1088
                internal class Step
 
1089
                {
 
1090
                        internal RebaseCommand.Action action;
 
1091
 
 
1092
                        internal AbbreviatedObjectId commit;
 
1093
 
 
1094
                        internal byte[] shortMessage;
 
1095
 
 
1096
                        internal Step(RebaseCommand.Action action)
 
1097
                        {
 
1098
                                this.action = action;
 
1099
                        }
 
1100
                }
 
1101
 
 
1102
                internal virtual PersonIdent ParseAuthor(byte[] raw)
 
1103
                {
 
1104
                        if (raw.Length == 0)
 
1105
                        {
 
1106
                                return null;
 
1107
                        }
 
1108
                        IDictionary<string, string> keyValueMap = new Dictionary<string, string>();
 
1109
                        for (int p = 0; p < raw.Length; )
 
1110
                        {
 
1111
                                int end = RawParseUtils.NextLF(raw, p);
 
1112
                                if (end == p)
 
1113
                                {
 
1114
                                        break;
 
1115
                                }
 
1116
                                int equalsIndex = RawParseUtils.Next(raw, p, '=');
 
1117
                                if (equalsIndex == end)
 
1118
                                {
 
1119
                                        break;
 
1120
                                }
 
1121
                                string key = RawParseUtils.Decode(raw, p, equalsIndex - 1);
 
1122
                                string value = RawParseUtils.Decode(raw, equalsIndex + 1, end - 2);
 
1123
                                p = end;
 
1124
                                keyValueMap.Put(key, value);
 
1125
                        }
 
1126
                        string name = keyValueMap.Get(GIT_AUTHOR_NAME);
 
1127
                        string email = keyValueMap.Get(GIT_AUTHOR_EMAIL);
 
1128
                        string time = keyValueMap.Get(GIT_AUTHOR_DATE);
 
1129
                        // the time is saved as <seconds since 1970> <timezone offset>
 
1130
                        long when = long.Parse(Sharpen.Runtime.Substring(time, 0, time.IndexOf(' '))) * 1000;
 
1131
                        string tzOffsetString = Sharpen.Runtime.Substring(time, time.IndexOf(' ') + 1);
 
1132
                        int multiplier = -1;
 
1133
                        if (tzOffsetString[0] == '+')
 
1134
                        {
 
1135
                                multiplier = 1;
 
1136
                        }
 
1137
                        int hours = System.Convert.ToInt32(Sharpen.Runtime.Substring(tzOffsetString, 1, 3
 
1138
                                ));
 
1139
                        int minutes = System.Convert.ToInt32(Sharpen.Runtime.Substring(tzOffsetString, 3, 
 
1140
                                5));
 
1141
                        // this is in format (+/-)HHMM (hours and minutes)
 
1142
                        // we need to convert into minutes
 
1143
                        int tz = (hours * 60 + minutes) * multiplier;
 
1144
                        if (name != null && email != null)
 
1145
                        {
 
1146
                                return new PersonIdent(name, email, when, tz);
 
1147
                        }
 
1148
                        return null;
 
1149
                }
 
1150
        }
 
1151
}