Skip to content

Instantly share code, notes, and snippets.

@kjk
Created March 21, 2019 00:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kjk/d0bae39b62d805605d4bf4b8bd677172 to your computer and use it in GitHub Desktop.
Save kjk/d0bae39b62d805605d4bf4b8bd677172 to your computer and use it in GitHub Desktop.
Compare output of 2 git log --format=oneline commands to see commits unique in each branch
package main
import (
"bytes"
"fmt"
"io/ioutil"
"strings"
)
/*
This code compares out of: git log --format="oneline"
I wanted to know what are the differences between 2 git branches of the same repository
To use it:
git log branch1 --format="oneline" >v41.log.txt
git log branch2 --format="oneline" >v40.log.txt
go run cmp-git-log.go
Change v41.log.txt name (and path1 / path2) as you wish.
Pitfalls: you might need to convert those files to utf-8. On Windows I got them
in UTF-16 format. Visual Studio Code can easily do it (click on encoding info in bottom-right
on the status line).
*/
const (
path1 = "v41.log.txt"
path2 = "v40.log.txt"
checkinURLPrefix = "https://github.com/ravendb/ravendb-jvm-client/commit/"
)
type CheckinInfo struct {
Hash string
Comment string
}
func must(err error) {
if err != nil {
panic(err)
}
}
// NormalizeNewlines normalizes \r\n (windows) and \r (mac)
// into \n (unix)
func NormalizeNewlines(d []byte) []byte {
// replace CR LF \r\n (windows) with LF \n (unix)
d = bytes.Replace(d, []byte{13, 10}, []byte{10}, -1)
// replace CF \r (mac) with LF \n (unix)
d = bytes.Replace(d, []byte{13}, []byte{10}, -1)
return d
}
func loadCheckinsMust(path string) []*CheckinInfo {
d, err := ioutil.ReadFile(path)
must(err)
d = NormalizeNewlines(d)
lines := strings.Split(string(d), "\n")
var res []*CheckinInfo
for i, line := range lines {
//fmt.Printf("line: '%s'\n", line)
line = strings.TrimSpace(line)
if len(line) == 0 {
continue
}
parts := strings.SplitN(line, " ", 2)
if len(parts) != 2 {
must(fmt.Errorf("unexpected line no %d: '%s'", i, line))
}
ci := &CheckinInfo{
Hash: strings.TrimSpace(parts[0]),
Comment: strings.TrimSpace(parts[1]),
}
res = append(res, ci)
}
return res
}
type MaybeCommon struct {
Checkin1 *CheckinInfo
Checkin2 *CheckinInfo
}
type Idx struct {
Idx1 int
Idx2 int
}
type CheckinsDiff struct {
Checkins1 []*CheckinInfo
Checkins2 []*CheckinInfo
// have the same hash and therefore the same checkins
Common []*CheckinInfo
// different hash but same comment so might be the same commit
MaybeCommon []*MaybeCommon
// different hash and comment so almost certainly different
NewIn1 []*CheckinInfo
NewIn2 []*CheckinInfo
}
func removeCheckinAt(checkinsPtr *[]*CheckinInfo, i int) {
a := *checkinsPtr
a = append(a[:i], a[i+1:]...)
*checkinsPtr = a
}
func reverseCheckins(checkinsPtr *[]*CheckinInfo) {
a := *checkinsPtr
last := len(a) - 1
for i := 0; i < len(a)/2; i++ {
a[i], a[last] = a[last], a[i]
last--
}
*checkinsPtr = a
}
func reverseMaybeCommon(checkinsPtr *[]*MaybeCommon) {
a := *checkinsPtr
last := len(a) - 1
for i := 0; i < len(a)/2; i++ {
a[i], a[last] = a[last], a[i]
last--
}
*checkinsPtr = a
}
func calcDiff(checkins1, checkins2 []*CheckinInfo) *CheckinsDiff {
diff := &CheckinsDiff{
Checkins1: checkins1,
Checkins2: checkins2,
}
// make a copy as we'll modify them
checkins1 = append([]*CheckinInfo{}, checkins1...)
checkins2 = append([]*CheckinInfo{}, checkins2...)
var common []*CheckinInfo
var maybeCommon []*MaybeCommon
// we assume that checkins are ordered with newest at the top
// common are most likely to be at the bottom, so we scan them from the end
i := len(checkins1) - 1
for i >= 0 {
ci := checkins1[i]
j := len(checkins2) - 1
L:
for j >= 0 {
ci2 := checkins2[j]
if ci.Hash == ci2.Hash {
common = append(common, ci)
removeCheckinAt(&checkins1, i)
removeCheckinAt(&checkins2, j)
break L
}
if ci.Comment == ci2.Comment {
mc := &MaybeCommon{
Checkin1: ci,
Checkin2: ci2,
}
maybeCommon = append(maybeCommon, mc)
removeCheckinAt(&checkins1, i)
removeCheckinAt(&checkins2, j)
break L
}
j--
}
i--
}
// those were gathered from the end, so reverse ordre to put newest first
reverseCheckins(&common)
reverseMaybeCommon(&maybeCommon)
diff.Common = common
diff.MaybeCommon = maybeCommon
// remaining checkins after removal are new
diff.NewIn1 = checkins1
diff.NewIn2 = checkins2
return diff
}
func printCheckins(checkins []*CheckinInfo) {
for _, c := range checkins {
fmt.Printf("* [ ] %s%s %s\n", checkinURLPrefix, c.Hash[:8], c.Comment)
}
}
func printMaybeCommon(a []*MaybeCommon) {
for _, mc := range a {
v41 := mc.Checkin1
v40 := mc.Checkin2
fmt.Printf("* [ ] v41: %s%s v40: %s%s %s\n", checkinURLPrefix, v41.Hash[:8], checkinURLPrefix, v40.Hash[:8], v40.Comment)
}
}
func main() {
v41checkins := loadCheckinsMust(path1)
v40checkins := loadCheckinsMust(path2)
diff := calcDiff(v41checkins, v40checkins)
//fmt.Printf("New in v40: %d\n", len(diff.NewIn2))
//fmt.Printf("New in v41: %d\n", len(diff.NewIn1))
fmt.Printf("New in v41 (%d), oldest first:\n", len(diff.NewIn1))
// have newest at the top, want oldest at the top
reverseCheckins(&diff.NewIn1)
printCheckins(diff.NewIn1)
fmt.Print("\n\n")
fmt.Printf("Maybe common checkins: %d\n", len(diff.MaybeCommon))
reverseMaybeCommon(&diff.MaybeCommon)
printMaybeCommon(diff.MaybeCommon)
fmt.Print("\n\n")
fmt.Printf("New in v40 (%d), oldest first:\n", len(diff.NewIn2))
// have newest at the top, want oldest at the top
reverseCheckins(&diff.NewIn2)
printCheckins(diff.NewIn2)
fmt.Print("\n\n")
fmt.Printf("Common checkins: %d\n", len(diff.Common))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment