8000 Add archive as tar functionality · GiTechLab/libgit2sharp@36d40bd · GitHub
[go: up one dir, main page]

Skip to content

Commit 36d40bd

Browse files
committed
Add archive as tar functionality
Implemented as an extension method of `ObjectDatabase`. Lots of logic taken from [archive-tar.c](https://github.com/git/git/blob/master/archive-tar.c).
1 parent f2d5b19 commit 36d40bd

File tree

5 files changed

+336
-51
lines changed

5 files changed

+336
-51
lines changed

LibGit2Sharp/Core/StringExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace LibGit2Sharp.Core
4+
{
5+
internal static class StringExtensions
6+
{
7+
public static int OctalToInt32(this string octal)
8+
{
9+
return Convert.ToInt32(octal, 8);
10+
}
11+
}
12+
}

LibGit2Sharp/Core/TarWriter.cs

Lines changed: 193 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public void Dispose()
5454
#endregion
5555

5656
public void Write(
57-
string fileName,
57+
FilePath filePath,
5858
Stream data,
5959
DateTimeOffset modificationTime,
6060
int mode,
@@ -64,18 +64,28 @@ public void Write(
6464
string userName,
6565
string groupName,
6666
string deviceMajorNumber,
67-
string deviceMinorNumber)
67+
string deviceMinorNumber,
68+
string entrySha,
69+
bool isLink)
6870
{
69-
WriteHeader(fileName, modificationTime, (data != null)?data.Length:0, mode, userId, groupId, typeflag, userName, groupName, deviceMajorNumber, deviceMinorNumber);
70-
// folders have no data
71-
if (data != null)
71+
FileNameExtendedHeader fileNameExtendedHeader = FileNameExtendedHeader.Parse(filePath.Posix, entrySha);
72+
LinkExtendedHeader linkExtendedHeader = ParseLink(isLink, data, entrySha);
73+
74+
WriteExtendedHeader(fileNameExtendedHeader, linkExtendedHeader, entrySha, modificationTime);
75+
76+
// Note: in case of links, we won't add a content, but the size in the header will still be != 0. It seems strange, but it seem to be what git.git is doing?
77+
WriteHeader(fileNameExtendedHeader.Name, fileNameExtendedHeader.Prefix, modificationTime, (data != null) ? data.Length : 0, mode,
78+
userId, groupId, typeflag, linkExtendedHeader.Link, userName, groupName, deviceMajorNumber, deviceMinorNumber);
79+
80+
// folders have no data, and so do links
81+
if (data != null && !isLink)
7282
{
73-
WriteContent(data.Length, data);
83+
WriteContent(data.Length, data, OutStream);
7484
}
75-
AlignTo512(data.Length, false);
85+
AlignTo512((data != null) ? data.Length : 0, false);
7686
}
7787

78-
protected void WriteContent(long count, Stream data)
88+
protected void WriteContent(long count, Stream data, Stream dest)
7989
{
8090
var buffer = new byte[1024];
8191

@@ -85,7 +95,7 @@ protected void WriteContent(long count, Stream data)
8595
if (bytesRead < 0)
8696
throw new IOException("TarWriter unable to read from provided stream");
8797

88-
OutStream.Write(buffer, 0, bytesRead);
98+
dest.Write(buffer, 0, bytesRead);
8999
count -= bytesRead;
90100
}
91101
if (count > 0)
@@ -97,12 +107,12 @@ protected void WriteContent(long count, Stream data)
97107
{
98108
while (count > 0)
99109
{
100-
OutStream.WriteByte(0);
110+
dest.WriteByte(0);
101111
--count;
102112
}
103113
}
104114
else
105-
OutStream.Write(buffer, 0, bytesRead);
115+
dest.Write(buffer, 0, bytesRead);
106116
}
107117
}
108118

@@ -118,23 +128,89 @@ protected void AlignTo512(long size, bool acceptZero)
118128
}
119129

120130
protected void WriteHeader(
121-
string name,
131+
string fileName,
132+
string namePrefix,
122133
DateTimeOffset lastModificationTime,
123134
long count,
124135
int mode,
125136
string userId,
126137
string groupId,
127138
char typeflag,
139+
string link,
128140
string userName,
129141
string groupName,
130142
string deviceMajorNumber,
131143
string deviceMinorNumber)
132144
{
133-
var tarHeader = new UsTarHeader(name, lastModificationTime, count, mode, userId, groupId, typeflag, userName, groupName, deviceMajorNumber, deviceMinorNumber);
145+
var tarHeader = new UsTarHeader(fileName, namePrefix, lastModificationTime, count, mode,
146+
userId, groupId, typeflag, link, userName, groupName, deviceMajorNumber, deviceMinorNumber);
134147
var header = tarHeader.GetHeaderValue();
135148
OutStream.Write(header, 0, header.Length);
136149
}
137150

151+
private LinkExtendedHeader ParseLink(bool isLink, Stream data, string entrySha)
152+
{
153+
if (!isLink)
154+
{
155+
return new LinkExtendedHeader(string.Empty, string.Empty, false);
156+
}
157+
158+
using (var dest = new MemoryStream())
159+
{
160+
WriteContent(data.Length, data, dest);
161+
dest.Seek(0, SeekOrigin.Begin);
162+
163+
using (var linkStream = new StreamReader(dest))
164+
{
165+
string link = linkStream.ReadToEnd();
166+
167+
if (data.Length > 100)
168+
{
169+
return new LinkExtendedHeader(link, string.Format("see %s.paxheader{0}", entrySha), true);
170+
}
171+
172+
return new LinkExtendedHeader(link, link, false);
173+
}
174+
}
175+
}
176+
177+
private void WriteExtendedHeader(FileNameExtendedHeader fileNameExtendedHeader, LinkExtendedHeader linkExtendedHeader, string entrySha,
178+
DateTimeOffset modificationTime)
179+
{
180+
string extHeader = string.Empty;
181+
182+
if (fileNameExtendedHeader.NeedsExtendedHeaderEntry)
183+
{
184+
extHeader += BuildKeyValueExtHeader("path", fileNameExtendedHeader.InitialPath);
185+
}
186+
187+
if (linkExtendedHeader.NeedsExtendedHeaderEntry)
188+
{
189+
extHeader += BuildKeyValueExtHeader("linkpath", linkExtendedHeader.InitialLink);
190+
}
191+
192+
if (string.IsNullOrEmpty(extHeader))
193+
{
194+
return;
195+
}
196+
197+
using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(extHeader)))
198+
{
199+
Write(string.Format("{0}.paxheader", entrySha), stream, modificationTime, "666".OctalToInt32(),
200+
"0", "0", 'x', "root", "root", "0", "0", entrySha, false);
201+
}
202+
}
203+
204+
private string BuildKeyValueExtHeader(string key, string value)
205+
{
206+
// "%u %s=%s\n"
207+
int len = key.Length + value.Length + 3;
208+
for (int i = len; i > 9; i /= 10)
209+
len++;
210+
211+
return string.Format("{0} {1}={2}\n", len, key, value);
212+
}
213+
138214
/// <summary>
139215
/// UsTar header implementation.
140216
/// </summary>
@@ -150,20 +226,22 @@ private class UsTarHeader
150226
private readonly string userId;
151227
private readonly string groupId;
152228
private readonly char typeflag;
229+
private readonly string link;
153230
private readonly string deviceMajorNumber;
154231
private readonly string deviceMinorNumber;
155-
private string namePrefix;
156-
private string fileName;
157-
232+
private readonly string namePrefix;
233+
private readonly string fileName;
158234

159235
public UsTarHeader(
160-
string filePath,
236+
string fileName,
237+
string namePrefix,
161238
DateTimeOffset lastModificationTime,
162239
long size,
163240
int mode,
164241
string userId,
165242
string groupId,
166243
char typeflag,
244+
string link,
167245
string userName,
168246
string groupName,
169247
string deviceMajorNumber,
@@ -195,6 +273,10 @@ public UsTarHeader(
195273
{
196274
throw new ArgumentException("ustar deviceMinorNumber cannot be longer than 7 characters.", "deviceMinorNumber");
197275
}
276+
if (link.Length > 100)
277+
{
278+
throw new ArgumentException("ustar link cannot be longer than 100 characters.", "link");
279+
}
198280

199281
#endregion
200282

@@ -206,43 +288,12 @@ public UsTarHeader(
206288
this.userName = userName;
207289
this.groupName = groupName;
208290
this.typeflag = typeflag;
291+
this.link = link;
209292
this.deviceMajorNumber = deviceMajorNumber.PadLeft(7, '0');
210293
this.deviceMinorNumber = deviceMinorNumber.PadLeft(7, '0');
211-
212-
ParseFileName(filePath);
213-
}
214-
215-
private void ParseFileName(string fullFilePath)
216-
{
217-
var posixPath = fullFilePath.Replace('/', Path.DirectorySeparatorChar);
218-
219-
if (posixPath.Length > 100)
220-
{
221-
if (posixPath.Length > 255)
222-
{
223-
throw new ArgumentException(string.Format("ustar filePath ({0}) cannot be longer than 255 chars", posixPath));
224-
}
225-
int position = posixPath.Length - 100;
226294

227-
// Find first path separator in the remaining 100 chars of the file name
228-
while (!Equals('/', posixPath[position]))
229-
{
230-
++position;
231-
if (position == posixPath.Length)
232-
{
233-
break;
234-
}
235-
}
236-
if (position == posixPath.Length)
237-
position = posixPath.Length - 100;
238-
namePrefix = posixPath.Substring(0, position);
239-
fileName = posixPath.Substring(position, posixPath.Length - position);
240-
}
241-
else
242-
{
243-
namePrefix = string.Empty;
244-
fileName = posixPath;
245-
}
295+
this.fileName = fileName;
296+
this.namePrefix = namePrefix;
246297
}
247298

248299
public byte[] GetHeaderValue()
@@ -257,6 +308,7 @@ public byte[] GetHeaderValue()
257308
Encoding.ASCII.GetBytes(Convert.ToString(size, 8).PadLeft(11, '0')).CopyTo(buffer, 124);
258309
Encoding.ASCII.GetBytes(unixTime).CopyTo(buffer, 136);
259310
buffer[156] = Convert.ToByte(typeflag);
311+
Encoding.ASCII.GetBytes(link).CopyTo(buffer, 157);
260312

261313
Encoding.ASCII.GetBytes(magic).CopyTo(buffer, 257); // Mark header as ustar
262314
Encoding.ASCII.GetBytes(version).CopyTo(buffer, 263);
@@ -301,5 +353,95 @@ private static byte[] AlignTo12(byte[] bytes)
301353
return retVal;
302354
}
303355
}
356+
357+
private class FileNameExtendedHeader
358+
{
359+
private readonly string prefix;
360+
private readonly string name;
361+
private readonly string initialPath;
362+
private readonly bool needsExtendedHeaderEntry;
363+
364+
private FileNameExtendedHeader(string initialPath, string prefix, string name, bool needsExtendedHeaderEntry)
365+
{
366+
this.initialPath = initialPath;
367+
this.prefix = prefix;
368+
this.name = name;
369+
this.needsExtendedHeaderEntry = needsExtendedHeaderEntry;
370+
}
371+
372+
public bool NeedsExtendedHeaderEntry
373+
{
374+
get { return needsExtendedHeaderEntry; }
375+
}
376+
377+
public string Name
378+
{
379+
get { return name; }
380+
}
381+
382+
public string Prefix
383+
{
384+
get { return prefix; }
385+
}
386+
387+
public string InitialPath
388+
{
389+
get { return initialPath; }
390+
}
391+
392+
/// <summary>
393+
/// Logic taken from https://github.com/git/git/blob/master/archive-tar.c
394+
/// </summary>
395+
public static FileNameExtendedHeader Parse(string posixPath, string entrySha)
396+
{
397+
if (posixPath.Length > 100)
398+
{
399+
// Need to increment by one because while loop decrements first before testing for path separator
400+
int position = Math.Min(156, posixPath.Length);
401+
402+
while (--position > 0 && !Equals('/', posixPath[position]))
403+
{ }
404+
405+
int remaining = posixPath.Length - position - 1;
406+
if (remaining < 100 && position > 0)
407+
{
408+
return new FileNameExtendedHeader(posixPath, posixPath.Substring(0, position), posixPath.Substring(position, posixPath.Length - position), false);
409+
}
410+
411+
return new FileNameExtendedHeader(posixPath, string.Empty, string.Format("{0}.data", entrySha), true);
412+
}
413+
414+
return new FileNameExtendedHeader(posixPath, string.Empty, posixPath, false);
415+
}
416+
}
417+
418+
private class LinkExtendedHeader
419+
{
420+
private readonly string initialLink;
421+
private readonly string link;
422+
private readonly bool needsExtendedHeaderEntry;
423+
424+
public LinkExtendedHeader(string initialLink, string link, bool needsExtendedHeaderEntry)
425+
{
426+
this.initialLink = initialLink;
427+
this.link = link;
428+
this.needsExtendedHeaderEntry = needsExtendedHeaderEntry;
429+
}
430+
431+
public string InitialLink
432+
{
433+
get { return initialLink; }
434+
}
435+
436+
public bool NeedsExtendedHeaderEntry
437+
{
438+
get { return needsExtendedHeaderEntry; }
439+
}
440+
441+
public string Link
442+
{
443+
get { return link; }
444+
}
445+
}
304446
}
305447
}

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@
145145
<Compile Include="SubmoduleStatus.cs" />
146146
<Compile Include="SubmoduleUpdate.cs" />
147147
<Compile Include="UnmergedIndexEntriesException.cs" />
148+
<Compile Include="Core\StringExtensions.cs" />
149+
<Compile Include="ObjectDatabaseExtensions.cs" />
148150
<Compile Include="Commit.cs" />
149151
<Compile Include="CommitLog.cs" />
150152
<Compile Include="Configuration.cs" />
@@ -222,6 +224,7 @@
222224
<Compile Include="Note.cs" />
223225
<Compile Include="RepositoryNotFoundException.cs" />
224226
<Compile Include="TagFetchMode.cs" />
227+
<Compile Include="TarArchiver.cs" />
225228
<Compile Include="TransferProgress.cs" />
226229
<Compile Include="TreeChanges.cs" />
227230
<Compile Include="TreeEntryChanges.cs" />

0 commit comments

Comments
 (0)
0