| // Copyright (C) 2016 The Android Open Source Project |
| // |
| // 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 |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // The branch-diff command compares two git repos looking for changes that |
| // are missing in one compared to the other. |
| package main |
| |
| import ( |
| "flag" |
| "fmt" |
| "regexp" |
| |
| "android.googlesource.com/platform/tools/gpu/client/git" |
| "android.googlesource.com/platform/tools/gpu/framework/app" |
| "android.googlesource.com/platform/tools/gpu/framework/file" |
| "android.googlesource.com/platform/tools/gpu/framework/log" |
| ) |
| |
| var ( |
| src = flag.String("src", "", "the source directory") |
| dst = flag.String("dst", "", "the destination directory") |
| cnt = flag.Int("c", 100, "maximum number of CLs to consider") |
| ) |
| |
| func main() { |
| app.ShortHelp = "branch-diff: A tool to compare two git repos for changes that exist in src and not dst." |
| app.Run(run) |
| } |
| |
| func run(ctx log.Context) error { |
| if *src == "" { |
| app.Usage(ctx, "Missing src directory") |
| return nil |
| } |
| if *dst == "" { |
| app.Usage(ctx, "Missing dst directory") |
| return nil |
| } |
| src, err := git.New(file.Abs(*src).System()) |
| if err != nil { |
| return ctx.WrapError(err, "Invalid src directory") |
| } |
| dst, err := git.New(file.Abs(*dst).System()) |
| if err != nil { |
| return ctx.WrapError(err, "Invalid dst directory") |
| } |
| srcCLs, err := src.Log(ctx, *cnt) |
| if err != nil { |
| return ctx.WrapError(err, "src log") |
| } |
| dstCLs, err := dst.Log(ctx, *cnt) |
| if err != nil { |
| return ctx.WrapError(err, "dst log") |
| } |
| |
| got := map[string]git.ChangeList{} |
| for _, cl := range dstCLs { |
| if changeID := parseChangeID(cl.Description); changeID != "" { |
| got[changeID] = cl |
| } |
| } |
| |
| for _, cl := range srcCLs { |
| if changeID := parseChangeID(cl.Description); changeID != "" { |
| if _, found := got[changeID]; !found { |
| parent, err := src.Parent(ctx, cl) |
| if err != nil { |
| return ctx.WrapError(err, "getting parent") |
| } |
| patch, err := src.GetPatch(ctx, parent.SHA, cl.SHA) |
| if err != nil { |
| return ctx.WrapError(err, "creating patch") |
| } |
| canApply, err := dst.CanApplyPatch(ctx, patch) |
| if err != nil { |
| return ctx.WrapError(err, "can apply") |
| } |
| applyable := '❌' |
| if canApply { |
| applyable = '✅' |
| } |
| fmt.Printf("%c %v %-10.10s %v\n", applyable, cl.SHA.String()[:8], cl.GetLdapOrAuthor(), cl.Subject) |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| var reChangeID = regexp.MustCompile("Change-Id: (I.*)") |
| |
| func parseChangeID(description string) string { |
| matches := reChangeID.FindStringSubmatch(description) |
| if len(matches) < 2 { |
| return "" |
| } |
| return matches[1] |
| } |