Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support local-to-remote mirroring #717

Merged
merged 14 commits into from
Oct 27, 2022
Merged

Support local-to-remote mirroring #717

merged 14 commits into from
Oct 27, 2022

Conversation

minwoox
Copy link
Contributor

@minwoox minwoox commented Aug 22, 2022

Motivation
We currently support only remote-to-local mirroring. We should support the opposite direction as well.

Modifications:

  • Implement GitMirror#mirrorToLocalRemote() which used to throw UnsupportedOperationException before.

Result:

Motivation
We currently support only remote-to-local mirroring. We should support the opposite direction as well.

Modifications:
- Implement `GitMirror#mirrorToLocalRemote()` which threw `UnsupportedOperationException` before.

Result:
- Close line#53
- You can now enable mirroring from Central Dogma to a remote Git server.
@minwoox minwoox added this to the 0.57.1 milestone Aug 22, 2022
@codecov
Copy link

codecov bot commented Aug 22, 2022

Codecov Report

Base: 64.95% // Head: 65.11% // Increases project coverage by +0.15% 🎉

Coverage data is based on head (52187d1) compared to base (0cf990c).
Patch coverage: 79.46% of modified lines in pull request are covered.

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #717      +/-   ##
============================================
+ Coverage     64.95%   65.11%   +0.15%     
- Complexity     3227     3259      +32     
============================================
  Files           349      349              
  Lines         13474    13670     +196     
  Branches       1454     1485      +31     
============================================
+ Hits           8752     8901     +149     
- Misses         3896     3928      +32     
- Partials        826      841      +15     
Impacted Files Coverage Δ
...erver/internal/mirror/DefaultMirroringService.java 65.90% <0.00%> (ø)
...ntraldogma/server/internal/mirror/MirrorState.java 100.00% <ø> (ø)
...inecorp/centraldogma/server/mirror/MirrorUtil.java 76.47% <ø> (ø)
...centraldogma/server/internal/mirror/GitMirror.java 79.09% <80.00%> (-0.17%) ⬇️
...necorp/centraldogma/testing/internal/TestUtil.java 83.33% <100.00%> (+1.51%) ⬆️
...aldogma/client/armeria/CompositeEndpointGroup.java 75.00% <0.00%> (-18.75%) ⬇️
...centraldogma/server/internal/api/WatchService.java 76.27% <0.00%> (-3.39%) ⬇️
.../linecorp/centraldogma/client/AbstractWatcher.java 76.00% <0.00%> (-2.67%) ⬇️
...ntraldogma/server/internal/mirror/GitWithAuth.java 31.25% <0.00%> (+0.89%) ⬆️
... and 1 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review in progress. Don't need to address the comments at the moment. I am going to review the remaining things tomorrow. Mostly looking good though. 😉

@minwoox
Copy link
Contributor Author

minwoox commented Sep 26, 2022

@ikhoon Addressed all the comments. 😉

@ikhoon
Copy link
Contributor

ikhoon commented Sep 27, 2022

The master branch is merged into the working branch to run in Apple Silicon.

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left minor comments and questions.

@Override
public void apply(DirCacheEntry ent) {
try {
ent.setObjectId(inserter.insert(Constants.OBJ_BLOB, text.getBytes(UTF_8)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we eventually convert text into bytes, should we directly write JsonNode into bytes and use it as is? It would allocate fewer objects.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we need to convert to String first because we need to use the pretty printer. If we use bytes, the mirror repository would see something like
{"a": {"foo":"bar"}} instead of

{
  "a" :  {
    "foo": "bar"
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I think this is reasonable:

  • For remote git repositories, we pretty print
  • For local storage, we save the compact json

Copy link
Contributor Author

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ikhoon All addressed. PTAL. 😄

@Override
public void apply(DirCacheEntry ent) {
try {
ent.setObjectId(inserter.insert(Constants.OBJ_BLOB, text.getBytes(UTF_8)));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we need to convert to String first because we need to use the pretty printer. If we use bytes, the mirror repository would see something like
{"a": {"foo":"bar"}} instead of

{
  "a" :  {
    "foo": "bar"
  }
}

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about the late review 😅 I think this PR looks pretty much done 👍 Left some minor comments


git.push()
.setRefSpecs(new RefSpec(headBranchRefName))
.setForce(true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can potentially overwrite commits. (e.g. multiple local repositories are pushed to a single remote repository, mis-operation can overwrite a repository, etc..)

If there is a conflict, what do you think of just failing the current iteration and allowing to update on the next iteration of mirror?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a conflict, what do you think of just failing the current iteration and allowing to update on the next iteration of mirror?

That's a good suggestion. 👍

Comment on lines +433 to +438
final Stream<Map.Entry<String, Entry<?>>> entryStream =
localRawHeadEntries.entrySet()
.stream();
if (ignoreNode == null) {
// Use HashMap to manipulate it.
return entryStream.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit; to avoid allocating an extra Stream object

Suggested change
final Stream<Map.Entry<String, Entry<?>>> entryStream =
localRawHeadEntries.entrySet()
.stream();
if (ignoreNode == null) {
// Use HashMap to manipulate it.
return entryStream.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
if (ignoreNode == null) {
// Use HashMap to manipulate it.
return localRawHeadEntries;
}
final Stream<Map.Entry<String, Entry<?>>> entryStream = localRawHeadEntries.entrySet().stream();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot just return localRawHeadEntries because it can be a singleton map that is not modifiable.
We do remove operation on the returned map.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see 😅

throw new UnsupportedOperationException();
try (Git git = openGit(workDir)) {
final String headBranchRefName = Constants.R_HEADS + remoteBranch();
final ObjectId headCommitId = fetchRemoteHeadAndGetCommitId(git, headBranchRefName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question) Just curious, what happens if the branch doesn't exist in the remote repository? Will an exception be thrown? (as opposed to creating a branch if it doesn't exist)

I think it's fine to throw an exception, just want to make sure the behavior is known/defined.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually checked and verified an exception is thrown 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking it. 😉

// Use InsertText to store the content in pretty format
final String newContent = newJsonNode.toPrettyString() + '\n';
applyPathEdit(dirCache, new InsertText(pathString, inserter, newContent));
return newContent.length();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit; technically String.length() isn't really the number of bytes (although I guess this is a more lenient upper-bound so this discrepancy isn't very critical)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, because it's not critical let's leave it as is.
Will get back and use
reader.getObjectSize(inserted, ObjectReader.OBJ_ANY) if it becomes a problem. 😉

/**
* Removes {@code \r} and appends {@code \n} on the last line if it does not end with {@code \n}.
*/
private static String sanitizeText(String text) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q) Is it possible to use a common method for this logic? (with storage side)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this comment wasn't addressed. Is it possible to dedup this method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah forgot to leave a comment about this. 😄
I didn't intentionally dedup logic because of this PR #681
Let me cleanup the logic in the PR! 😉

@Override
public void apply(DirCacheEntry ent) {
try {
ent.setObjectId(inserter.insert(Constants.OBJ_BLOB, text.getBytes(UTF_8)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I think this is reasonable:

  • For remote git repositories, we pretty print
  • For local storage, we save the compact json

" \"direction\": \"" + direction + "\"," +
" \"localRepo\": \"" + localRepo + "\"," +
(localPath != null ? "\"localPath\": \"" + localPath + "\"," : "") +
" \"remoteUri\": \"" + gitUri + firstNonNull(remotePath, "") + '"' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of also adding this to GitMirrorTest as well?

Suggested change
" \"remoteUri\": \"" + gitUri + firstNonNull(remotePath, "") + '"' +
" \"remoteUri\": \"" + gitUri + firstNonNull(remotePath, "") + '"' +
" \"schedule\": \"0 0 0 1 1 ? 2099\"," +

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Added. 😉

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a minor comment but looks good to me 👍 Thanks @minwoox 👍 🙇 🚀 !

Comment on lines +433 to +438
final Stream<Map.Entry<String, Entry<?>>> entryStream =
localRawHeadEntries.entrySet()
.stream();
if (ignoreNode == null) {
// Use HashMap to manipulate it.
return entryStream.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see 😅

/**
* Removes {@code \r} and appends {@code \n} on the last line if it does not end with {@code \n}.
*/
private static String sanitizeText(String text) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this comment wasn't addressed. Is it possible to dedup this method?

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!!! 🚀💯

@minwoox minwoox merged commit 3baa748 into line:master Oct 27, 2022
@minwoox minwoox deleted the cdToGit branch October 27, 2022 03:57
@minwoox
Copy link
Contributor Author

minwoox commented Oct 27, 2022

Thanks, @ikhoon and @jrhee17 for reviewing. 😉

@minwoox minwoox modified the milestones: 0.58.0, 0.57.4 Nov 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CD-to-Git mirroring
3 participants