Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8813da5
Add docs for working with forks.
ojarjur Aug 13, 2018
a3b4308
Add skeletons for the various `git appraise fork ...` commands
ojarjur Aug 14, 2018
b0afaf4
Change `fork add` to take the owner emails as a comma-separated list
ojarjur Aug 14, 2018
ad819ac
Shorten the name of the `fork add` owner flag to make it consistent w…
ojarjur Aug 14, 2018
e099dbb
Added API surface area for adding, removing, and getting the forks of…
ojarjur Aug 17, 2018
8ec98c8
Add the `--include-forks` flag to the `pull` subcommand
ojarjur Aug 22, 2018
60181fd
Move the forks collection methods to a dedicated package
ojarjur Sep 5, 2018
b447bc4
Fill in the fork commands code
ojarjur Sep 5, 2018
742bae7
Fill in the git-repository implementation for pulling and pushing forks
ojarjur Sep 6, 2018
bd7636e
Make the git operations on forks work when there are no remote forks
ojarjur Sep 6, 2018
9792e0a
Make the logic for checking ref existence more resilient
ojarjur Sep 8, 2018
640d8d5
Implement the logic to list forks
ojarjur Sep 14, 2018
05b7e0b
Fix formatting for forks summary output
ojarjur Sep 14, 2018
d6849eb
Add support for adding forks
ojarjur Sep 17, 2018
09e179c
Don't error out when pulling with forks
ojarjur Sep 17, 2018
bf2de09
Remove unused `ShowAll` method
ojarjur Sep 17, 2018
f8bd6ef
Make the docs for the excludeFork option more consistent
ojarjur Sep 17, 2018
1311191
Implement the logic for deleting forks
ojarjur Sep 18, 2018
fd8e9e5
Fix bugs in the logic to merge forks refs
ojarjur Sep 18, 2018
3c91046
Implement fetching from forks
ojarjur Sep 18, 2018
5d90571
Don't re-write trees that already exist in the repository
ojarjur Sep 18, 2018
30583a8
Simplify the repository.Tree API
ojarjur Sep 18, 2018
f6953ae
Support loading diffs from reviews in fork branches
ojarjur Sep 22, 2018
60bfa7e
Support reading review refs from remotes other than origin
ojarjur Sep 22, 2018
99321ed
Merge review requests and comments from forks
ojarjur Sep 23, 2018
2e2d4aa
Add an integration test for forks
ojarjur Oct 2, 2018
d7d8adb
Make the forks test a bit simpler and realistic
ojarjur Oct 2, 2018
f218638
Make the forks test run multiple pulls to test the behavior when ther…
ojarjur Oct 5, 2018
48a2064
Restructure the fork pulling code to be simpler and to perform less w…
ojarjur Oct 5, 2018
38beeb6
Check committer instead of author for reviews from forks
ojarjur Oct 8, 2018
8d0f4c5
Check comment locations before merging them from a fork
ojarjur Oct 10, 2018
0eb8db8
Merge branch 'master' into ojarjur/forks
ojarjur Nov 13, 2018
4f4956c
Make the repo method for pushing more general
ojarjur Nov 13, 2018
b071f2f
Make the addForkFlagSet name match the command
ojarjur Jan 3, 2019
ede713a
Integrate support for forks with the new GPG support
ojarjur Jan 4, 2019
ae7411b
Remove unnecessary network calls to list refs in a remote repository …
ojarjur Jan 5, 2019
d621be5
Move the logic for pulling from forks into the `fork` package
ojarjur Jan 5, 2019
170aefc
Merge branch 'master' into ojarjur/forks
ojarjur Apr 14, 2020
eca3ab8
Update github mirror action to not try to overwrite the checked-out b…
ojarjur Apr 14, 2020
6c07ca4
Merge branch 'master' into ojarjur/forks
ojarjur Jun 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add an integration test for forks
  • Loading branch information
ojarjur committed Oct 2, 2018
commit 2e2d4aa5e93c23c959677190718cf333091332d3
25 changes: 19 additions & 6 deletions commands/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,32 @@ func pull(repo repository.Repo, args []string) error {
return repo.PullNotesAndArchive(remote, notesRefPattern, archiveRefPattern)
}
if err := repo.PullNotesForksAndArchive(remote, notesRefPattern, fork.Ref, archiveRefPattern); err != nil {
return err
return fmt.Errorf("failure pulling review metadata from the remote %q: %v", remote, err)
}
forks, err := fork.List(repo)
if err != nil {
return err
return fmt.Errorf("failure listing the forks: %v", err)
}
var g errgroup.Group
for _, f := range forks {
g.Go(func() error {
return fork.Pull(repo, f)
})
func(f *fork.Fork) {
g.Go(func() error {
if err := f.Fetch(repo); err != nil {
return fmt.Errorf("failure pulling from the fork %q: %v", f.Name, err)
}
return nil
})
}(f)
}
if err := g.Wait(); err != nil {
return err
}
for _, f := range forks {
if err := f.Merge(repo); err != nil {
return fmt.Errorf("failure merging from the fork %q: %v", f.Name, err)
}
}
return g.Wait()
return nil
}

var pullCmd = &Command{
Expand Down
212 changes: 170 additions & 42 deletions fork/fork.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ package fork
import (
"crypto/sha1"
"fmt"
"strings"

"github.com/google/git-appraise/repository"
"github.com/google/git-appraise/review/comment"
Expand Down Expand Up @@ -65,6 +66,27 @@ func New(name, url string, owners []string) *Fork {
}
}

func localRefForFork(remoteRef, forkName string) (string, error) {
if strings.Contains(remoteRef, ":") || strings.Contains(remoteRef, "+") {
return "", fmt.Errorf("invalid remote ref %q", remoteRef)
}
if strings.HasPrefix(remoteRef, "refs/notes/") {
// Older versions of git require all notes refs to be under "refs/notes"
return fmt.Sprintf("refs/notes/forks/%s/%s", forkName, remoteRef), nil
}
return fmt.Sprintf("refs/forks/%s/%s", forkName, remoteRef), nil
}

func filteredRefForFork(remoteRef, forkName string) (string, error) {
if strings.Contains(remoteRef, ":") || strings.Contains(remoteRef, "+") {
return "", fmt.Errorf("invalid remote ref %q", remoteRef)
}
if !strings.HasPrefix(remoteRef, "refs/notes/devtools/") {
return "", fmt.Errorf("only devtools notes refs can be filtered")
}
return fmt.Sprintf("refs/notes/filteredForks/%s/%s", forkName, remoteRef), nil
}

func forkPathFromName(forkName string) []string {
forkHash := fmt.Sprintf("%x", sha1.Sum([]byte(forkName)))
return []string{forkHash[0:1], forkHash[1:2], forkHash[2:]}
Expand Down Expand Up @@ -316,27 +338,69 @@ func newlyAddedNotes(oldNotes []repository.Note, newNotes []repository.Note) []r
return addedNotes
}

func (fork *Fork) mergeOwnerRequests(repo repository.Repo) error {
forkRequestsRef := fmt.Sprintf("refs/forks/%s/%s", fork.Name, request.Ref)
func createMergeCommit(repo repository.Repo, ref, commitToMerge, message string) error {
if hasRef, err := repo.HasRef(ref); err != nil {
return fmt.Errorf("failure checking the existence of the ref %q: %v", ref, err)
} else if !hasRef {
// There is nothing to merge into
return nil
}
refDetails, err := repo.GetCommitDetails(ref)
if err != nil {
return fmt.Errorf("failure reading the ref %q: %v", ref, err)
}
refCommit, err := repo.GetCommitHash(ref)
if err != nil {
return fmt.Errorf("failure reading the commit for the ref %q: %v", ref, err)
}
parents := []string{refCommit, commitToMerge}
mergeCommit, err := repo.CreateCommitFromTreeHash(refDetails.Tree, parents, message)
if err != nil {
return fmt.Errorf("failure creating a merge commit for %q: %v", ref, err)
}
return repo.SetRef(ref, mergeCommit, refCommit)
}

func (fork *Fork) filterOwnerRequests(repo repository.Repo) error {
forkRequestsRef, err := localRefForFork(request.Ref, fork.Name)
if err != nil {
return err
}
forkRequestsRefCommit, err := repo.GetCommitHash(forkRequestsRef)
if err != nil {
// There are no fork requests to merge
return nil
}
if isAncestor, err := repo.IsAncestor(forkRequestsRefCommit, request.Ref); err != nil {
filteredForkRequestsRef, err := filteredRefForFork(request.Ref, fork.Name)
if err != nil {
return err
} else if isAncestor {
}

isMerged := false
existingNotesMap := map[string][]repository.Note{}
if hasRef, err := repo.HasRef(filteredForkRequestsRef); err != nil {
return fmt.Errorf("failure checking the existence of the review requests ref: %v", err)
} else if hasRef {
isMerged, err = repo.IsAncestor(forkRequestsRefCommit, filteredForkRequestsRef)
if err != nil {
return fmt.Errorf("failure checking the ancestry of the review requests ref: %v", err)
}
existingNotesMap, err = repo.GetAllNotes(filteredForkRequestsRef)
if err != nil {
return fmt.Errorf("failure reading the contents of the review requests ref: %v", err)
}
}
if isMerged {
// All fork requests have already been merged
return nil
}
existingNotesMap, err := repo.GetAllNotes(request.Ref)
if err != nil {
return err
}
newNotesMap, err := repo.GetAllNotes(forkRequestsRef)
if err != nil {
return err
}
if len(newNotesMap) == 0 {
return fmt.Errorf("failed to load the notes for the ref %q", forkRequestsRef)
}
for obj, newNotes := range newNotesMap {
commitDetails, err := repo.GetCommitDetails(obj)
if err != nil {
Expand All @@ -352,6 +416,9 @@ func (fork *Fork) mergeOwnerRequests(repo repository.Repo) error {
newNotes = newlyAddedNotes(existingNotes, newNotes)
}
for _, note := range newNotes {
if len(note) == 0 {
continue
}
r, err := request.Parse(note)
if err != nil {
continue
Expand All @@ -361,41 +428,47 @@ func (fork *Fork) mergeOwnerRequests(repo repository.Repo) error {
}
}
for _, note := range notesToAdd {
if err := repo.AppendNote(request.Ref, obj, note); err != nil {
return err
if err := repo.AppendNote(filteredForkRequestsRef, obj, note); err != nil {
return fmt.Errorf("failure merging in a review request: %v", err)
}
}
}
notesRefDetails, err := repo.GetCommitDetails(request.Ref)
if err != nil {
return err
}
notesRefCommit, err := repo.GetCommitHash(request.Ref)
return createMergeCommit(repo, filteredForkRequestsRef, forkRequestsRefCommit, fmt.Sprintf("merging in requests from the fork %q", fork.Name))
}

func (fork *Fork) filterOwnerComments(repo repository.Repo) error {
forkCommentsRef, err := localRefForFork(comment.Ref, fork.Name)
if err != nil {
return err
}
parents := []string{notesRefCommit, forkRequestsRefCommit}
mergeCommit, err := repo.CreateCommitFromTreeHash(notesRefDetails.Tree, parents, fmt.Sprintf("merging in requests from the fork %s", fork.Name))
return repo.SetRef(request.Ref, mergeCommit, notesRefCommit)
}

func (fork *Fork) mergeOwnerComments(repo repository.Repo) error {
forkCommentsRef := fmt.Sprintf("refs/forks/%s/%s", fork.Name, comment.Ref)
forkCommentsRefCommit, err := repo.GetCommitHash(forkCommentsRef)
if err != nil {
// There are no fork comments to merge
return nil
}
if isAncestor, err := repo.IsAncestor(forkCommentsRefCommit, comment.Ref); err != nil {
return err
} else if isAncestor {
// The fork comments have already been merged
return nil
}
existingNotesMap, err := repo.GetAllNotes(comment.Ref)
filteredForkCommentsRef, err := filteredRefForFork(comment.Ref, fork.Name)
if err != nil {
return err
}

isMerged := false
existingNotesMap := map[string][]repository.Note{}
if hasRef, err := repo.HasRef(filteredForkCommentsRef); err != nil {
return fmt.Errorf("failure checking the existence of the comments ref: %v", err)
} else if hasRef {
isMerged, err = repo.IsAncestor(forkCommentsRefCommit, filteredForkCommentsRef)
if err != nil {
return fmt.Errorf("failure checking the ancestry of the comments ref: %v", err)
}
existingNotesMap, err = repo.GetAllNotes(filteredForkCommentsRef)
if err != nil {
return fmt.Errorf("failure reading the contents of the comments ref: %v", err)
}
}
if isMerged {
// All fork comments have already been merged
return nil
}
newNotesMap, err := repo.GetAllNotes(forkCommentsRef)
if err != nil {
return err
Expand All @@ -416,39 +489,94 @@ func (fork *Fork) mergeOwnerComments(repo repository.Repo) error {
}
}
for _, note := range notesToAdd {
if err := repo.AppendNote(comment.Ref, obj, note); err != nil {
if err := repo.AppendNote(filteredForkCommentsRef, obj, note); err != nil {
return err
}
}
}
notesRefDetails, err := repo.GetCommitDetails(comment.Ref)
return createMergeCommit(repo, filteredForkCommentsRef, forkCommentsRefCommit, fmt.Sprintf("merging in comments from the fork %q", fork.Name))
}

func appendAllNotes(repo repository.Repo, destination, source string) error {
sourceCommit, err := repo.GetCommitHash(source)
if err != nil {
// There is nothing to do
return nil
}
if hasRef, err := repo.HasRef(destination); err != nil {
return err
} else if !hasRef {
// The destination does not yet exist, simply copy the new commit over as-is.
return repo.SetRef(destination, sourceCommit, "")
}
if isMerged, err := repo.IsAncestor(sourceCommit, destination); err != nil {
return err
} else if isMerged {
// The notes have already been merged
return nil
}
existingNotesMap, err := repo.GetAllNotes(destination)
if err != nil {
return err
}
notesRefCommit, err := repo.GetCommitHash(comment.Ref)
newNotesMap, err := repo.GetAllNotes(source)
if err != nil {
return err
}
parents := []string{notesRefCommit, forkCommentsRefCommit}
mergeCommit, err := repo.CreateCommitFromTreeHash(notesRefDetails.Tree, parents, fmt.Sprintf("merging in comments from the fork %s", fork.Name))
return repo.SetRef(comment.Ref, mergeCommit, notesRefCommit)
for obj, newNotes := range newNotesMap {
if existingNotes, ok := existingNotesMap[obj]; !ok {
newNotes = newlyAddedNotes(existingNotes, newNotes)
}
var notesToAdd []string
for _, note := range newNotes {
notesToAdd = append(notesToAdd, string(note))
}
allNotesToAdd := strings.Join(notesToAdd, "\n")
if err := repo.AppendNote(destination, obj, repository.Note([]byte(allNotesToAdd))); err != nil {
return err
}
}
return nil
}

func Pull(repo repository.Repo, fork *Fork) error {
func (fork *Fork) Fetch(repo repository.Repo) error {
for _, url := range fork.URLS {
var refSpecs []string
for _, ref := range fork.Refs {
refSpecs = append(refSpecs, fmt.Sprintf("+%s:refs/forks/%s/%s", ref, fork.Name, ref))
localRef, err := localRefForFork(ref, fork.Name)
if err != nil {
return err
}
refSpec := fmt.Sprintf("+%s:%s", ref, localRef)
refSpecs = append(refSpecs, refSpec)
}
if err := repo.Fetch(url, refSpecs); err != nil {
return err
return fmt.Errorf("failure fetching from the fork: %v", err)
}
if err := fork.mergeOwnerRequests(repo); err != nil {
return err
if err := fork.filterOwnerRequests(repo); err != nil {
return fmt.Errorf("failure merging the review requests: %v", err)
}
if err := fork.mergeOwnerComments(repo); err != nil {
return err
if err := fork.filterOwnerComments(repo); err != nil {
return fmt.Errorf("failure merging the comments: %v", err)
}
}
return nil
}

func (fork *Fork) Merge(repo repository.Repo) error {
filteredForkCommentsRef, err := filteredRefForFork(comment.Ref, fork.Name)
if err != nil {
return err
}
if err := appendAllNotes(repo, comment.Ref, filteredForkCommentsRef); err != nil {
return err
}
filteredForkRequestsRef, err := filteredRefForFork(request.Ref, fork.Name)
if err != nil {
return err
}
if err := appendAllNotes(repo, request.Ref, filteredForkRequestsRef); err != nil {
return err
}
return nil
}
Loading