cmd/zoekt-mirror-bitbucket-server: delete stale repos
This change adds a method to delete stale repos for the BitBucket Server mirror.
Closes #91.
Change-Id: Iff3e643e3aec16c19fb51b89aab77212063815b6
diff --git a/cmd/zoekt-indexserver/config.go b/cmd/zoekt-indexserver/config.go
index 0a73bef..d976904 100644
--- a/cmd/zoekt-indexserver/config.go
+++ b/cmd/zoekt-indexserver/config.go
@@ -195,7 +195,7 @@
cmd.Args = append(cmd.Args, c.CGitURL)
} else if c.BitBucketServerURL != "" {
cmd = exec.Command("zoekt-mirror-bitbucket-server",
- "-dest", repoDir, "-url", c.BitBucketServerURL)
+ "-dest", repoDir, "-url", c.BitBucketServerURL, "-delete")
if c.BitBucketServerProject != "" {
cmd.Args = append(cmd.Args, "-project", c.BitBucketServerProject)
}
diff --git a/cmd/zoekt-mirror-bitbucket-server/main.go b/cmd/zoekt-mirror-bitbucket-server/main.go
index 790d075..19270ec 100644
--- a/cmd/zoekt-mirror-bitbucket-server/main.go
+++ b/cmd/zoekt-mirror-bitbucket-server/main.go
@@ -1,5 +1,3 @@
-// Copyright 2016 Google Inc. All rights reserved.
-//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -41,6 +39,7 @@
serverUrl := flag.String("url", "", "BitBucket Server url")
credentialsFile := flag.String("credentials", ".bitbucket-credentials", "file holding BitBucket Server credentials")
project := flag.String("project", "", "project to mirror")
+ deleteRepos := flag.Bool("delete", false, "delete missing repos")
namePattern := flag.String("name", "", "only clone repos whose name matches the given regexp.")
excludePattern := flag.String("exclude", "", "don't mirror repos whose names match this regexp.")
projectType := flag.String("type", "", "only clone repos whose type matches the given string. "+
@@ -137,6 +136,36 @@
if err := cloneRepos(destDir, rootURL.Host, repos, password); err != nil {
log.Fatalf("cloneRepos: %v", err)
}
+
+ if *deleteRepos {
+ if err := deleteStaleRepos(*dest, filter, repos); err != nil {
+ log.Fatalf("deleteStaleRepos: %v", err)
+ }
+ }
+}
+
+func deleteStaleRepos(destDir string, filter *gitindex.Filter, repos []bitbucketv1.Repository) error {
+ var baseURL string
+ if len(repos) > 0 {
+ baseURL = repos[0].Links.Self[0].Href
+ } else {
+ return nil
+ }
+ u, err := url.Parse(baseURL)
+ if err != nil {
+ return err
+ }
+ u.Path = ""
+
+ names := map[string]struct{}{}
+ for _, r := range repos {
+ names[filepath.Join(u.Host, r.Project.Key, r.Slug+".git")] = struct{}{}
+ }
+
+ if err := gitindex.DeleteRepos(destDir, u, names, filter); err != nil {
+ log.Fatalf("deleteRepos: %v", err)
+ }
+ return nil
}
func IsValidProjectType(projectType string) bool {
diff --git a/cmd/zoekt-mirror-github/main.go b/cmd/zoekt-mirror-github/main.go
index c1848ca..7a2078b 100644
--- a/cmd/zoekt-mirror-github/main.go
+++ b/cmd/zoekt-mirror-github/main.go
@@ -170,39 +170,17 @@
}
u.Path = user
- paths, err := gitindex.ListRepos(destDir, u)
- if err != nil {
- return err
- }
-
- names := map[string]bool{}
+ names := map[string]struct{}{}
for _, r := range repos {
u, err := url.Parse(*r.HTMLURL)
if err != nil {
return err
}
- names[filepath.Join(u.Host, u.Path+".git")] = true
+ names[filepath.Join(u.Host, u.Path+".git")] = struct{}{}
}
- var toDelete []string
- for _, p := range paths {
- if filter.Include(filepath.Base(p)) && !names[p] {
- toDelete = append(toDelete, p)
- }
- }
-
- if len(toDelete) > 0 {
- log.Printf("deleting repos %v", toDelete)
- }
-
- var errs []string
- for _, d := range toDelete {
- if err := os.RemoveAll(filepath.Join(destDir, d)); err != nil {
- errs = append(errs, err.Error())
- }
- }
- if len(errs) > 0 {
- return fmt.Errorf("errors: %v", errs)
+ if err := gitindex.DeleteRepos(destDir, u, names, filter); err != nil {
+ log.Fatalf("deleteRepos: %v", err)
}
return nil
}
diff --git a/cmd/zoekt-mirror-gitlab/main.go b/cmd/zoekt-mirror-gitlab/main.go
index 8e156de..7f1fa40 100644
--- a/cmd/zoekt-mirror-gitlab/main.go
+++ b/cmd/zoekt-mirror-gitlab/main.go
@@ -146,40 +146,18 @@
return err
}
- paths, err := gitindex.ListRepos(destDir, u)
- if err != nil {
- return err
- }
-
- names := map[string]bool{}
+ names := map[string]struct{}{}
for _, p := range projects {
u, err := url.Parse(p.HTTPURLToRepo)
if err != nil {
return err
}
- names[filepath.Join(u.Host, u.Path)] = true
+ names[filepath.Join(u.Host, u.Path)] = struct{}{}
}
- var toDelete []string
- for _, p := range paths {
- if filter.Include(p) && !names[p] {
- toDelete = append(toDelete, p)
- }
- }
-
- if len(toDelete) > 0 {
- log.Printf("deleting repos %v", toDelete)
- }
-
- var errs []string
- for _, d := range toDelete {
- if err := os.RemoveAll(filepath.Join(destDir, d)); err != nil {
- errs = append(errs, err.Error())
- }
- }
- if len(errs) > 0 {
- return fmt.Errorf("errors: %v", errs)
+ if err := gitindex.DeleteRepos(destDir, u, names, filter); err != nil {
+ log.Fatalf("deleteRepos: %v", err)
}
return nil
}
diff --git a/gitindex/delete.go b/gitindex/delete.go
new file mode 100644
index 0000000..f53e1af
--- /dev/null
+++ b/gitindex/delete.go
@@ -0,0 +1,41 @@
+package gitindex
+
+import (
+ "fmt"
+ "log"
+ "net/url"
+ "os"
+ "path/filepath"
+)
+
+// DeleteRepos deletes stale repos under a specific path in disk. The `names`
+// argument stores names of repos retrieved from the git hosting site
+// and is used along with the `filter` argument to decide on repo deletion.
+func DeleteRepos(baseDir string, urlPrefix *url.URL, names map[string]struct{}, filter *Filter) error {
+ paths, err := ListRepos(baseDir, urlPrefix)
+ if err != nil {
+ return err
+ }
+ var toDelete []string
+ for _, p := range paths {
+ _, exists := names[p]
+ if filter.Include(filepath.Base(p)) && !exists {
+ toDelete = append(toDelete, p)
+ }
+ }
+
+ if len(toDelete) > 0 {
+ log.Printf("deleting repos %v", toDelete)
+ }
+
+ var errs []string
+ for _, d := range toDelete {
+ if err := os.RemoveAll(filepath.Join(baseDir, d)); err != nil {
+ errs = append(errs, err.Error())
+ }
+ }
+ if len(errs) > 0 {
+ return fmt.Errorf("errors: %v", errs)
+ }
+ return nil
+}
diff --git a/gitindex/delete_test.go b/gitindex/delete_test.go
new file mode 100644
index 0000000..571a25d
--- /dev/null
+++ b/gitindex/delete_test.go
@@ -0,0 +1,83 @@
+package gitindex
+
+import (
+ "io/ioutil"
+ "net/url"
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+func TestDeleteRepos(t *testing.T) {
+ dir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatalf("TempDir: %v", err)
+ }
+
+ if err := createSubmoduleRepo(dir); err != nil {
+ t.Error("createSubmoduleRepo", err)
+ }
+
+ reposBefore, err := FindGitRepos(dir)
+ if err != nil {
+ t.Error("FindGitRepos", err)
+ }
+
+ gotBefore := map[string]struct{}{}
+ for _, r := range reposBefore {
+ p, err := filepath.Rel(dir, r)
+ if err != nil {
+ t.Fatalf("Relative: %v", err)
+ }
+
+ gotBefore[p] = struct{}{}
+ }
+
+ wantBefore := map[string]struct{}{
+ "gerrit.googlesource.com/bdir.git": {},
+ "gerrit.googlesource.com/sub/bdir.git": {},
+ "adir/.git": {},
+ "bdir/.git": {},
+ "gerrit.googlesource.com/adir.git": {},
+ }
+
+ if !reflect.DeepEqual(gotBefore, wantBefore) {
+ t.Fatalf("got %v want %v", gotBefore, wantBefore)
+ }
+
+ aURL, _ := url.Parse("http://gerrit.googlesource.com")
+ aURL.Path = "sub"
+ names := map[string]struct{}{
+ "bdir/.git": {},
+ "gerrit.googlesource.com/adir.git": {},
+ }
+ filter, _ := NewFilter("", "")
+
+ err = DeleteRepos(dir, aURL, names, filter)
+ if err != nil {
+ t.Fatalf("DeleteRepos: %T", err)
+ }
+ reposAfter, err := FindGitRepos(dir)
+ if err != nil {
+ t.Error("FindGitRepos", err)
+ }
+
+ gotAfter := map[string]struct{}{}
+ for _, r := range reposAfter {
+ p, err := filepath.Rel(dir, r)
+ if err != nil {
+ t.Fatalf("Relative: %v", err)
+ }
+
+ gotAfter[p] = struct{}{}
+ }
+ wantAfter := map[string]struct{}{
+ "gerrit.googlesource.com/bdir.git": {},
+ "adir/.git": {},
+ "bdir/.git": {},
+ "gerrit.googlesource.com/adir.git": {}}
+
+ if !reflect.DeepEqual(gotAfter, wantAfter) {
+ t.Errorf("got %v want %v", gotAfter, wantAfter)
+ }
+}