Skip to content

Commit 1ceae96

Browse files
committed
feat: show content diff
Now you can use flag `--show-diff` or `-i` to show changes in all modified files in format similar to git diff output.
1 parent a4d2b1b commit 1ceae96

File tree

10 files changed

+440
-101
lines changed

10 files changed

+440
-101
lines changed

README.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ Download archive for your platform from [releases page](https://github.com/unrav
3939

4040
When you are on Linux you can use following commands to download and install latest release:
4141

42-
wget https://github.com/unravela/indiff/releases/download/v0.1.0/indiff_0.1.0_Linux_x86_64.tar.gz
43-
tar -xzvf ./indiff_0.1.0_Linux_x86_64.tar.gz -C /tmp/
42+
wget https://github.com/unravela/indiff/releases/download/v0.2.0/indiff_0.2.0_Linux_x86_64.tar.gz
43+
tar -xzvf ./indiff_0.2.0_Linux_x86_64.tar.gz -C /tmp/
4444
sudo mv /tmp/indiff /usr/local/bin
4545

4646

@@ -101,6 +101,34 @@ To check changes between revision tagged with `v1.0.0` and latest commit run:
101101

102102
> Instead of tag name you can use commit hash. Tildes and carets are supported too so you can use expressions like `HEAD^` or `v.1.0.0~2`.
103103
104+
### Content diff
105+
106+
If you want to see the details about what exactly was changed you can use `-i` flag and indiff will print changes for each modified file in format similar to `git diff` output.
107+
108+
Running following command:
109+
110+
indiff -f v1.0.0 -i en,de
111+
112+
Can result in output like this:
113+
114+
de: missing translation of: en/first.xml
115+
de: missing translation of: en/second.md
116+
de: missing translation of: en/subone/second.md
117+
de: modified only base: en/first.md: de/first.md
118+
diff en/first.md
119+
@@ -1 +1,3 @@
120+
# First file
121+
+
122+
+Content of file
123+
de: modified base and translation: en/section/one.md: de/section/one.md
124+
diff en/section/one.md
125+
@@ -0,0 +1 @@
126+
+# First file in section one
127+
diff de/section/one.md
128+
@@ -0,0 +1 @@
129+
+# Erste Datei in Abschnitt eins
130+
131+
104132
### It works without git too
105133

106134
If you project is not versioned with git you can still use indiff to look for missing translation files.

cmd/main.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ func main() {
7474
Value: false,
7575
Aliases: []string{"a"},
7676
},
77+
&cli.BoolFlag{
78+
Name: "show-diff",
79+
Usage: "Print diff for each modified file",
80+
Value: false,
81+
Aliases: []string{"i"},
82+
},
7783
},
7884
Writer: os.Stderr,
7985
HideHelpCommand: true,
@@ -126,7 +132,10 @@ func run(c *cli.Context) error {
126132
}
127133

128134
// parse revision range
129-
revisionRange := git.NewRange(c.String("from-revision"), c.String("to-revision"))
135+
revisionRange := &git.Range{
136+
Older: c.String("from-revision"),
137+
Newer: c.String("to-revision"),
138+
}
130139
isGitAllowed := !c.Bool("no-git")
131140

132141
// collect bundle
@@ -149,7 +158,11 @@ func run(c *cli.Context) error {
149158
}
150159

151160
// render
152-
r := render.NewPlain(root, !c.Bool("absolute-paths"))
161+
r := &render.Plain{
162+
RootPath: root,
163+
ShowRelativePaths: !c.Bool("absolute-paths"),
164+
ShowDiff: c.Bool("show-diff"),
165+
}
153166
r.Render(os.Stdout, diffs)
154167

155168
return nil

diff.go

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,42 @@ func (m *Missing) String() string {
5151
return fmt.Sprintf("Missing{ base: %s, lang: %s }", m.base, m.lang)
5252
}
5353

54+
// Modification holds modified file changes made to this file
55+
type Modification struct {
56+
file *File
57+
patch string // TODO: use some structured type instead of string to be able to control output
58+
}
59+
60+
// NewModification turns File to Modification (modified file) with changes in given patch
61+
func NewModification(file *File, patch string) *Modification {
62+
return &Modification{file: file, patch: patch}
63+
}
64+
65+
// Modified turns this file to Modification with changes in given patch
66+
func (f *File) Modified(patch string) *Modification {
67+
return NewModification(f, patch)
68+
}
69+
5470
// ModifiedBase says that base file was modified but it's translation was not
5571
type ModifiedBase struct {
56-
base *File
72+
base *Modification
5773
translation *File
5874
// TODO: what exactly was added / removed
5975
}
6076

6177
// NewModifiedBase creates new ModifiedBase file difference
62-
func NewModifiedBase(base *File, translation *File) *ModifiedBase {
78+
func NewModifiedBase(base *Modification, translation *File) *ModifiedBase {
6379
return &ModifiedBase{base: base, translation: translation}
6480
}
6581

6682
// Base points to file in base language which was modified
6783
func (m *ModifiedBase) Base() *File {
68-
return m.base
84+
return m.base.file
85+
}
86+
87+
// BasePatch returns text representation of changes made to file in base language
88+
func (m *ModifiedBase) BasePatch() string {
89+
return m.base.patch
6990
}
7091

7192
// Translation points to file which equivalent in base language was modified
@@ -79,36 +100,46 @@ func (m *ModifiedBase) Lang() string {
79100
}
80101

81102
func (m *ModifiedBase) String() string {
82-
return fmt.Sprintf("ModifiedBase{ base: %s, translation: %s }", m.base, m.translation)
103+
return fmt.Sprintf("ModifiedBase{ base: %s, translation: %s }", m.base.file, m.translation)
83104
}
84105

85106
// ModifiedBoth says that base file and it's translation was modified
86107
type ModifiedBoth struct {
87-
base *File
88-
translation *File
108+
base *Modification
109+
translation *Modification
89110
// TODO: what exactly was added / removed
90111
}
91112

92113
// NewModifiedBoth creates new ModifiedBoth file difference
93-
func NewModifiedBoth(base *File, translation *File) *ModifiedBoth {
114+
func NewModifiedBoth(base *Modification, translation *Modification) *ModifiedBoth {
94115
return &ModifiedBoth{base: base, translation: translation}
95116
}
96117

97118
// Base points to file in base language which was modified
98119
func (m *ModifiedBoth) Base() *File {
99-
return m.base
120+
return m.base.file
121+
}
122+
123+
// BasePatch returns text representation of changes made to file in base language
124+
func (m *ModifiedBoth) BasePatch() string {
125+
return m.base.patch
126+
}
127+
128+
// TranslationPatch returns text representation of changes made to translation file
129+
func (m *ModifiedBoth) TranslationPatch() string {
130+
return m.translation.patch
100131
}
101132

102133
// Translation points to translation file which was modified
103134
func (m *ModifiedBoth) Translation() *File {
104-
return m.translation
135+
return m.translation.file
105136
}
106137

107138
// Lang is language of translation file
108139
func (m *ModifiedBoth) Lang() string {
109-
return m.translation.Lang
140+
return m.translation.file.Lang
110141
}
111142

112143
func (m *ModifiedBoth) String() string {
113-
return fmt.Sprintf("ModifiedBoth{ base: %s, translation: %s }", m.base, m.translation)
144+
return fmt.Sprintf("ModifiedBoth{ base: %s, translation: %s }", m.base.file, m.translation.file)
114145
}

git/changes.go

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package git
22

33
import (
44
"bytes"
5-
"path/filepath"
65

76
"github.com/pkg/errors"
87

@@ -11,60 +10,78 @@ import (
1110
"github.com/go-git/go-git/v5/utils/merkletrie/noder"
1211
)
1312

14-
// treeChanges holds changes
15-
type treeChanges struct {
16-
root string
17-
changes merkletrie.Changes
13+
// revChange holds one change between to revisions
14+
type revisionChange struct {
15+
underlying merkletrie.Change
16+
revisionRange *revisionRange
1817
}
1918

19+
// revisionChanges represent collection of changes between two revisions
20+
type revisionChanges []*revisionChange
21+
2022
// collectChanges collects changes in repo in given revisionRange
21-
func collectChanges(repo *git.Repository, revisionRange *Range) (*treeChanges, error) {
22-
// get trees for both sides of revision range
23-
olderTree, err := revisionRange.olderTree(repo)
24-
if err != nil {
25-
return nil, errors.Wrap(err, "Cannot get older revision tree")
26-
}
27-
newerTree, err := revisionRange.newerTree(repo)
28-
if err != nil {
29-
return nil, errors.Wrap(err, "Cannot get newer revision tree")
30-
}
31-
changes, err := merkletrie.DiffTree(olderTree, newerTree, diffTreeIsEquals)
23+
func collectChanges(repo *git.Repository, revisionRange *revisionRange) (revisionChanges, error) {
24+
older := revisionRange.older.root
25+
newer := revisionRange.newer.root
26+
originalChanges, err := merkletrie.DiffTree(older, newer, diffTreeIsEquals)
3227
if err != nil {
3328
return nil, errors.Wrap(err, "Cannot resolve difference between older and newer revision tree")
3429
}
3530

36-
// resolve repo root path
37-
tree, err := repo.Worktree()
38-
if err != nil {
39-
return nil, errors.Wrap(err, "Cannot get worktree of repository")
31+
// enrich changes with revision references
32+
changes := make(revisionChanges, len(originalChanges))
33+
for i, c := range originalChanges {
34+
changes[i] = &revisionChange{
35+
underlying: c,
36+
revisionRange: revisionRange,
37+
}
4038
}
41-
root := tree.Filesystem.Root()
4239

43-
return &treeChanges{root: root, changes: changes}, nil
40+
return changes, nil
4441
}
4542

4643
// forEachCreatedOrModified invokes given function f for each change with action Inserted or Modified
47-
func (t *treeChanges) forEachCreatedOrModified(f func(path string)) {
48-
for _, c := range t.changes {
49-
action, _ := c.Action()
44+
func (changes revisionChanges) forEachCreatedOrModified(f func(change *revisionChange)) {
45+
for _, c := range changes {
46+
action, _ := c.underlying.Action()
5047
if action == merkletrie.Insert || action == merkletrie.Modify {
51-
path := filepath.Join(t.root, c.To.String())
52-
f(path)
48+
f(c)
5349
}
5450
}
5551
}
5652

57-
// wasCreatedOrModified check if given path points to file which was changed with action Inserted or Modified
58-
func (t *treeChanges) wasCreatedOrModified(path string) bool {
59-
for _, c := range t.changes {
60-
action, _ := c.Action()
61-
if action == merkletrie.Insert || action == merkletrie.Modify {
62-
if path == filepath.Join(t.root, c.To.String()) {
63-
return true
64-
}
65-
}
53+
// fromPath returns path to file before change
54+
func (c *revisionChange) fromPath() string {
55+
return c.underlying.From.String()
56+
}
57+
58+
// fromContent reads content of the file before change
59+
func (c *revisionChange) fromContent() string {
60+
if c.fromPath() == "" {
61+
return ""
62+
}
63+
content, err := c.revisionRange.older.contentOf(c.fromPath())
64+
if err != nil {
65+
panic(err)
66+
}
67+
return content
68+
}
69+
70+
// toPath returns path to file after change
71+
func (c *revisionChange) toPath() string {
72+
return c.underlying.To.String()
73+
}
74+
75+
// toContent returns content of the file after change
76+
func (c *revisionChange) toContent() string {
77+
if c.toPath() == "" {
78+
return ""
79+
}
80+
content, err := c.revisionRange.newer.contentOf(c.toPath())
81+
if err != nil {
82+
panic(err)
6683
}
67-
return false
84+
return content
6885
}
6986

7087
// code below was copied from go-git as it was private but needed for merkletrie.DiffTree

0 commit comments

Comments
 (0)