Skip to content

Commit 6fa28d7

Browse files
committed
Add pack-refs command to the CLI
This command can be used to optimize storage of references. For a RefDirectory database, it packs non-symbolic, loose refs into packed-refs. By default, only the references under '$GIT_DIR/refs/tags' are packed. The '--all' option can be used to pack all the references under '$GIT_DIR/refs'. For Reftable, all refs are compacted into a single table. Change-Id: I92e786403f8638d35ae3037844a7ad89e8959e02
1 parent f295477 commit 6fa28d7

File tree

14 files changed

+317
-54
lines changed

14 files changed

+317
-54
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) 2024 Qualcomm Innovation Center, Inc.
3+
* and other copyright owners as documented in the project's IP log.
4+
*
5+
* This program and the accompanying materials are made available under the
6+
* terms of the Eclipse Distribution License v. 1.0 which is available at
7+
* https://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*/
11+
12+
package org.eclipse.jgit.pgm;
13+
14+
import static org.junit.Assert.assertEquals;
15+
import static org.junit.Assert.assertNotNull;
16+
import static org.junit.Assert.assertTrue;
17+
18+
import java.io.File;
19+
20+
import org.eclipse.jgit.api.Git;
21+
import org.eclipse.jgit.internal.storage.file.FileRepository;
22+
import org.eclipse.jgit.lib.CLIRepositoryTestCase;
23+
import org.eclipse.jgit.lib.ConfigConstants;
24+
import org.eclipse.jgit.lib.Constants;
25+
import org.eclipse.jgit.lib.Ref;
26+
import org.junit.Before;
27+
import org.junit.Test;
28+
29+
public class PackRefsTest extends CLIRepositoryTestCase {
30+
private Git git;
31+
32+
@Override
33+
@Before
34+
public void setUp() throws Exception {
35+
super.setUp();
36+
git = new Git(db);
37+
git.commit().setMessage("initial commit").call();
38+
}
39+
40+
@Test
41+
public void tagPacked() throws Exception {
42+
git.tag().setName("test").call();
43+
git.packRefs().call();
44+
assertEquals(Ref.Storage.PACKED,
45+
git.getRepository().exactRef("refs/tags/test").getStorage());
46+
}
47+
48+
@Test
49+
public void nonTagRefNotPackedWithoutAll() throws Exception {
50+
git.branchCreate().setName("test").call();
51+
git.packRefs().call();
52+
assertEquals(Ref.Storage.LOOSE,
53+
git.getRepository().exactRef("refs/heads/test").getStorage());
54+
}
55+
56+
@Test
57+
public void nonTagRefPackedWithAll() throws Exception {
58+
git.branchCreate().setName("test").call();
59+
git.packRefs().setAll(true).call();
60+
assertEquals(Ref.Storage.PACKED,
61+
git.getRepository().exactRef("refs/heads/test").getStorage());
62+
}
63+
64+
@Test
65+
public void refTableCompacted() throws Exception {
66+
((FileRepository) git.getRepository()).convertRefStorage(
67+
ConfigConstants.CONFIG_REF_STORAGE_REFTABLE, false, false);
68+
69+
git.commit().setMessage("test commit").call();
70+
File tableDir = new File(db.getDirectory(), Constants.REFTABLE);
71+
File[] reftables = tableDir.listFiles();
72+
assertNotNull(reftables);
73+
assertTrue(reftables.length > 2);
74+
75+
git.packRefs().call();
76+
77+
reftables = tableDir.listFiles();
78+
assertNotNull(reftables);
79+
assertEquals(2, reftables.length);
80+
}
81+
}

org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ org.eclipse.jgit.pgm.LsTree
2626
org.eclipse.jgit.pgm.Merge
2727
org.eclipse.jgit.pgm.MergeBase
2828
org.eclipse.jgit.pgm.MergeTool
29+
org.eclipse.jgit.pgm.PackRefs
2930
org.eclipse.jgit.pgm.Push
3031
org.eclipse.jgit.pgm.ReceivePack
3132
org.eclipse.jgit.pgm.Reflog

org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties

+2
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ updating=Updating {0}..{1}
257257
usage_Abbrev=Instead of using the default number of hexadecimal digits (which will vary according to the number of objects in the repository with a default of 7) of the abbreviated object name, use <n> digits, or as many digits as needed to form a unique object name. An <n> of 0 will suppress long format, only showing the closest tag.
258258
usage_addRenormalize=Apply the "clean" process freshly to tracked files to forcibly add them again to the index. This implies -u.
259259
usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time
260+
usage_All=Pack all refs, except hidden refs, broken refs, and symbolic refs.
260261
usage_AlwaysFallback=Show uniquely abbreviated commit object as fallback
261262
usage_bareClone=Make a bare Git repository. That is, instead of creating [DIRECTORY] and placing the administrative files in [DIRECTORY]/.git, make the [DIRECTORY] itself the $GIT_DIR.
262263
usage_extraArgument=Pass an extra argument to a merge driver. Currently supported are "-X ours" and "-X theirs".
@@ -300,6 +301,7 @@ usage_Match=Only consider tags matching the given glob(7) pattern or patterns, e
300301
usage_MergeBase=Find as good common ancestors as possible for a merge
301302
usage_MergesTwoDevelopmentHistories=Merges two development histories
302303
usage_PackKeptObjects=Include objects in packs locked by a ".keep" file when repacking
304+
usage_PackRefs=Pack heads and tags for efficient repository access
303305
usage_PreserveOldPacks=Preserve old pack files by moving them into the preserved subdirectory instead of deleting them after repacking
304306
usage_PrunePreserved=Remove the preserved subdirectory containing previously preserved old pack files before repacking, and before preserving more old pack files
305307
usage_ReadDirCache= Read the DirCache 100 times
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2024 Qualcomm Innovation Center, Inc.
3+
* and other copyright owners as documented in the project's IP log.
4+
*
5+
* This program and the accompanying materials are made available under the
6+
* terms of the Eclipse Distribution License v. 1.0 which is available at
7+
* https://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*/
11+
12+
package org.eclipse.jgit.pgm;
13+
14+
import org.eclipse.jgit.api.Git;
15+
import org.eclipse.jgit.api.errors.GitAPIException;
16+
import org.eclipse.jgit.lib.TextProgressMonitor;
17+
import org.kohsuke.args4j.Option;
18+
19+
@Command(common = true, usage = "usage_PackRefs")
20+
class PackRefs extends TextBuiltin {
21+
@Option(name = "--all", usage = "usage_All")
22+
private boolean all;
23+
24+
@Override
25+
protected void run() {
26+
Git git = Git.wrap(db);
27+
try {
28+
git.packRefs().setProgressMonitor(new TextProgressMonitor(errw))
29+
.setAll(all).call();
30+
} catch (GitAPIException e) {
31+
throw die(e.getMessage(), e);
32+
}
33+
}
34+
}

org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java

+19-12
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static org.junit.Assert.assertSame;
2020

2121
import java.io.File;
22-
import java.io.IOException;
2322
import java.nio.file.Files;
2423
import java.nio.file.Path;
2524
import java.util.concurrent.BrokenBarrierException;
@@ -31,6 +30,8 @@
3130
import java.util.concurrent.TimeUnit;
3231

3332
import org.eclipse.jgit.api.Git;
33+
import org.eclipse.jgit.api.PackRefsCommand;
34+
import org.eclipse.jgit.api.errors.GitAPIException;
3435
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
3536
import org.eclipse.jgit.lib.ConfigConstants;
3637
import org.eclipse.jgit.lib.Constants;
@@ -49,7 +50,7 @@ public void looseRefPacked() throws Exception {
4950
RevBlob a = tr.blob("a");
5051
tr.lightweightTag("t", a);
5152

52-
gc.packRefs();
53+
packRefs(false);
5354
assertSame(repo.exactRef("refs/tags/t").getStorage(), Storage.PACKED);
5455
}
5556

@@ -60,7 +61,7 @@ public void emptyRefDirectoryDeleted() throws Exception {
6061
String name = repo.findRef(ref).getName();
6162
Path dir = repo.getCommonDirectory().toPath().resolve(name).getParent();
6263
assertNotNull(dir);
63-
gc.packRefs();
64+
packRefs(true);
6465
assertFalse(Files.exists(dir));
6566
}
6667

@@ -75,9 +76,9 @@ public void concurrentOnlyOneWritesPackedRefs() throws Exception {
7576
Callable<Integer> packRefs = () -> {
7677
syncPoint.await();
7778
try {
78-
gc.packRefs();
79+
packRefs(false);
7980
return 0;
80-
} catch (IOException e) {
81+
} catch (GitAPIException e) {
8182
return 1;
8283
}
8384
};
@@ -102,7 +103,7 @@ public void whileRefLockedRefNotPackedNoError()
102103
"refs/tags/t1"));
103104
try {
104105
refLock.lock();
105-
gc.packRefs();
106+
packRefs(false);
106107
} finally {
107108
refLock.unlock();
108109
}
@@ -145,7 +146,7 @@ public boolean isForceUpdate() {
145146

146147
Future<Result> result2 = pool.submit(() -> {
147148
refUpdateLockedRef.await();
148-
gc.packRefs();
149+
packRefs(false);
149150
packRefsDone.await();
150151
return null;
151152
});
@@ -173,19 +174,20 @@ public void dontPackHEAD_nonBare() throws Exception {
173174
assertEquals(repo.exactRef("HEAD").getTarget().getName(),
174175
"refs/heads/master");
175176
assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
176-
gc.packRefs();
177+
PackRefsCommand packRefsCommand = git.packRefs().setAll(true);
178+
packRefsCommand.call();
177179
assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
178180
assertEquals(repo.exactRef("HEAD").getTarget().getName(),
179181
"refs/heads/master");
180182
assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
181183

182184
git.checkout().setName("refs/heads/side").call();
183-
gc.packRefs();
185+
packRefsCommand.call();
184186
assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
185187

186188
// check for detached HEAD
187189
git.checkout().setName(first.getName()).call();
188-
gc.packRefs();
190+
packRefsCommand.call();
189191
assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
190192
}
191193

@@ -208,17 +210,22 @@ public void dontPackHEAD_bare() throws Exception {
208210
assertEquals(repo.exactRef("HEAD").getTarget().getName(),
209211
"refs/heads/master");
210212
assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
211-
gc.packRefs();
213+
packRefs(true);
212214
assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
213215
assertEquals(repo.exactRef("HEAD").getTarget().getName(),
214216
"refs/heads/master");
215217
assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
216218

217219
// check for non-detached HEAD
218220
repo.updateRef(Constants.HEAD).link("refs/heads/side");
219-
gc.packRefs();
221+
packRefs(true);
220222
assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
221223
assertEquals(repo.exactRef("HEAD").getTarget().getObjectId(),
222224
second.getId());
223225
}
226+
227+
private void packRefs(boolean all) throws GitAPIException {
228+
new PackRefsCommand(repo).setAll(all).call();
229+
}
230+
224231
}

org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties

+2
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,8 @@ packInaccessible=Failed to access pack file {0}, caught {1} consecutive errors w
597597
packingCancelledDuringObjectsWriting=Packing cancelled during objects writing
598598
packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2}
599599
packRefs=Pack refs
600+
packRefsFailed=Packing refs failed
601+
packRefsSuccessful=Packed refs successfully
600602
packSizeNotSetYet=Pack size not yet set since it has not yet been received
601603
packTooLargeForIndexVersion1=Pack too large for index version 1
602604
packWasDeleted=Pack file {0} was deleted, removing it from pack list

org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java

+10
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,16 @@ public GarbageCollectCommand gc() {
713713
return new GarbageCollectCommand(repo);
714714
}
715715

716+
/**
717+
* Return a command object to execute a {@code PackRefs} command
718+
*
719+
* @return a {@link org.eclipse.jgit.api.PackRefsCommand}
720+
* @since 7.1
721+
*/
722+
public PackRefsCommand packRefs() {
723+
return new PackRefsCommand(repo);
724+
}
725+
716726
/**
717727
* Return a command object to find human-readable names of revisions.
718728
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright (c) 2024 Qualcomm Innovation Center, Inc.
3+
* and other copyright owners as documented in the project's IP log.
4+
*
5+
* This program and the accompanying materials are made available under the
6+
* terms of the Eclipse Distribution License v. 1.0 which is available at
7+
* https://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*/
11+
12+
package org.eclipse.jgit.api;
13+
14+
import java.io.IOException;
15+
16+
import org.eclipse.jgit.api.errors.GitAPIException;
17+
import org.eclipse.jgit.api.errors.JGitInternalException;
18+
import org.eclipse.jgit.internal.JGitText;
19+
import org.eclipse.jgit.lib.NullProgressMonitor;
20+
import org.eclipse.jgit.lib.ProgressMonitor;
21+
import org.eclipse.jgit.lib.Repository;
22+
23+
/**
24+
* Optimize storage of references.
25+
*
26+
* @since 7.1
27+
*/
28+
public class PackRefsCommand extends GitCommand<String> {
29+
private ProgressMonitor monitor;
30+
31+
private boolean all;
32+
33+
/**
34+
* Creates a new {@link PackRefsCommand} instance with default values.
35+
*
36+
* @param repo
37+
* the repository this command will be used on
38+
*/
39+
public PackRefsCommand(Repository repo) {
40+
super(repo);
41+
this.monitor = NullProgressMonitor.INSTANCE;
42+
}
43+
44+
/**
45+
* Set progress monitor
46+
*
47+
* @param monitor
48+
* a progress monitor
49+
* @return this instance
50+
*/
51+
public PackRefsCommand setProgressMonitor(ProgressMonitor monitor) {
52+
this.monitor = monitor;
53+
return this;
54+
}
55+
56+
/**
57+
* Specify whether to pack all the references.
58+
*
59+
* @param all
60+
* if <code>true</code> all the loose refs will be packed
61+
* @return this instance
62+
*/
63+
public PackRefsCommand setAll(boolean all) {
64+
this.all = all;
65+
return this;
66+
}
67+
68+
/**
69+
* Whether to pack all the references
70+
*
71+
* @return whether to pack all the references
72+
*/
73+
public boolean isAll() {
74+
return all;
75+
}
76+
77+
@Override
78+
public String call() throws GitAPIException {
79+
checkCallable();
80+
try {
81+
repo.getRefDatabase().packRefs(monitor, this);
82+
return JGitText.get().packRefsSuccessful;
83+
} catch (IOException e) {
84+
throw new JGitInternalException(JGitText.get().packRefsFailed, e);
85+
}
86+
}
87+
}

org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java

+2
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,8 @@ public static JGitText get() {
627627
/***/ public String packingCancelledDuringObjectsWriting;
628628
/***/ public String packObjectCountMismatch;
629629
/***/ public String packRefs;
630+
/***/ public String packRefsFailed;
631+
/***/ public String packRefsSuccessful;
630632
/***/ public String packSizeNotSetYet;
631633
/***/ public String packTooLargeForIndexVersion1;
632634
/***/ public String packWasDeleted;

0 commit comments

Comments
 (0)