Skip to content

Commit aebb2c1

Browse files
mauro3Datseris
authored andcommitted
WIP: Generating and storing a patch (#80)
* Generating and storing a patch * rename commit to gitcommit * added docs * Fix @tag! and tagsave after tag! argument re-ordering * minor doc
1 parent 8b34599 commit aebb2c1

File tree

6 files changed

+74
-35
lines changed

6 files changed

+74
-35
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# 0.8.0
22
* **[BREAKING]** : Slightly changed how `produce_or_load` uses `path` and interacts with `savename`, to better incorporate the changes done in version 0.6.0. `prefix` is now also supported.
3+
* `tag!` and co now also store the git diff patch if the repo is dirty (#80).
4+
* **[BREAKING]** : `tag!` now saves the commit information into a field `gitcommit` instead of just `commit`.
35

46
# 0.7.1
57
* `projectdir()` now warns if no project (other than the standard one) is

docs/src/save.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ For reproducibility reasons (and also to not go insane when asking "HOW DID I GE
3131
To this end we have some functions that can be used to ensure reproducibility:
3232

3333
```@docs
34-
gitdescribe
3534
tag!
3635
@tag!
36+
gitdescribe
37+
DrWatson.gitpatch
3738
```
3839

3940
Please notice that `tag!` will operate in place only when possible. If not possible then a new dictionary is returned. Also (importantly) these functions will **never error** as they are most commonly used when saving simulations and this could risk data not being saved!

src/saving_files.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ See also [`savename`](@ref).
2727
"""
2828
produce_or_load(c, f; kwargs...) = produce_or_load("", c, f; kwargs...)
2929
function produce_or_load(path::String, c, f;
30-
tag::Bool = true, gitpath = projectdir(), loadfile = true,
30+
tag::Bool = true, gitpath = projectdir(), storepatch = true, loadfile = true,
3131
suffix = "bson", prefix = default_prefix(c),
3232
force = false, verbose = true, kwargs...)
3333

@@ -50,7 +50,7 @@ function produce_or_load(path::String, c, f;
5050
try
5151
mkpath(dirname(s))
5252
if tag
53-
tagsave(s, file, false, gitpath)
53+
tagsave(s, file, false, gitpath, storepatch)
5454
else
5555
wsave(s, copy(file))
5656
end
@@ -70,13 +70,13 @@ end
7070
# tag saving #
7171
################################################################################
7272
"""
73-
tagsave(file::String, d::Dict [, safe = false, gitpath = projectdir()])
73+
tagsave(file::String, d::Dict [, safe = false, gitpath = projectdir(), storepatch = true])
7474
First [`tag!`](@ref) dictionary `d` and then save `d` in `file`.
7575
If `safe = true` save the file using [`safesave`](@ref).
7676
"""
7777
tagsave(file, d, p::String) = tagsave(file, d, false, p)
78-
function tagsave(file, d, safe = false, gitpath = projectdir(), s = nothing)
79-
d2 = tag!(d, gitpath, s)
78+
function tagsave(file, d, safe = false, gitpath = projectdir(), storepatch = true, s = nothing)
79+
d2 = tag!(d, gitpath, storepatch, s)
8080
mkpath(dirname(file))
8181
if safe
8282
safesave(file, copy(d2))
@@ -91,9 +91,9 @@ end
9191
Same as [`tagsave`](@ref) but also add a field `script` that records
9292
the local path of the script that called `@tagsave`, see [`@tag!`](@ref).
9393
"""
94-
macro tagsave(file, d, safe::Bool = false, gitpath = projectdir())
94+
macro tagsave(file, d, safe::Bool = false, gitpath = projectdir(), storepatch = true)
9595
s = QuoteNode(__source__)
96-
:(tagsave($(esc(file)), $(esc(d)), $(esc(safe)), $(esc(gitpath)), $s))
96+
:(tagsave($(esc(file)), $(esc(d)), $(esc(safe)), $(esc(gitpath)), $(esc(storepatch)), $s))
9797
end
9898

9999
################################################################################

src/saving_tools.jl

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,47 @@ function gitdescribe(gitpath = projectdir())
6565
return c
6666
end
6767

68-
@deprecate current_commit gitdescribe
68+
"""
69+
gitpatch(gitpath = projectdir())
70+
71+
Generates a patch describing the changes of a dirty repository
72+
compared to its last commit; i.e. what `git diff HEAD` produces.
73+
"""
74+
function gitpatch(gitpath = projectdir())
75+
try
76+
repo = LibGit2.GitRepo(gitpath)
77+
catch er
78+
@warn "The directory ('$gitpath') is not a Git repository, "*
79+
"returning `nothing` instead of a patch."
80+
return nothing
81+
end
82+
# tree = LibGit2.GitTree(repo, "HEAD^{tree}")
83+
# diff = LibGit2.diff_tree(repo, tree)
84+
# now there is no way to generate the patch with LibGit2.jl.
85+
# Instead use commands:
86+
patch = read(`git --git-dir=$(gitpath)/.git diff HEAD`, String)
87+
return patch
88+
end
6989

7090
"""
71-
tag!(d::Dict, gitpath = projectdir()) -> d
72-
Tag `d` by adding an extra field `commit` which will have as value
91+
tag!(d::Dict, gitpath = projectdir(), storepatch = true) -> d
92+
Tag `d` by adding an extra field `gitcommit` which will have as value
7393
the [`gitdescribe`](@ref) of the repository at `gitpath` (by default
74-
the project's gitpath). Do nothing if a key `commit` already exists or
75-
if the Git repository is not found.
94+
the project's gitpath). Do nothing if a key `gitcommit` already exists
95+
or if the Git repository is not found. If the git repository is dirty,
96+
i.e. there are un-commited changes, then the output of `git diff HEAD`
97+
is stored in the field `gitpatch`. Note that patches for binary files
98+
are not stored.
7699
77100
Notice that if `String` is not a subtype of the value type of `d` then
78-
a new dictionary is created and returned. Otherwise the operation
79-
is inplace (and the dictionary is returned again).
101+
a new dictionary is created and returned. Otherwise the operation is
102+
inplace (and the dictionary is returned again).
103+
104+
To restore a repository to the state of a particular model-run do:
105+
- checkout the relevant commit with `git checkout xyz` where
106+
xyz is the value stored
107+
- apply the patch `git apply patch`, where the string stored
108+
in the `gitpatch` field needs to be written to the file `patch`.
80109
81110
## Examples
82111
```julia
@@ -87,25 +116,32 @@ Dict{Symbol,Int64} with 2 entries:
87116
88117
julia> tag!(d)
89118
Dict{Symbol,Any} with 3 entries:
90-
:y => 4
91-
:commit => "96df587e45b29e7a46348a3d780db1f85f41de04"
92-
:x => 3
119+
:y => 4
120+
:gitcommit => "96df587e45b29e7a46348a3d780db1f85f41de04"
121+
:x => 3
93122
```
94123
"""
95-
function tag!(d::Dict{K, T}, gitpath = projectdir(), source = nothing) where {K, T}
124+
function tag!(d::Dict{K, T}, gitpath = projectdir(), storepatch = true, source = nothing) where {K, T}
96125

97126
c = gitdescribe(gitpath)
98-
c === nothing && return d
99-
if haskey(d, K("commit"))
100-
@warn "The dictionary already has a key named `commit`. We won't "*
127+
patch = gitpatch(gitpath)
128+
c === nothing && return d # gitpath is not a git repo
129+
if haskey(d, K("gitcommit"))
130+
@warn "The dictionary already has a key named `gitcommit`. We won't "*
101131
"add any Git information."
102132
return d
103133
end
104134
if String <: T
105-
d[K("commit")] = c
135+
d[K("gitcommit")] = c
136+
if patch!=""
137+
d[K("gitpatch")] = patch
138+
end
106139
else
107140
d = Dict{K, promote_type(T, String)}(d)
108-
d[K("commit")] = c
141+
d[K("gitcommit")] = c
142+
if patch!=""
143+
d[K("gitpatch")] = patch
144+
end
109145
end
110146
if source != nothing
111147
if haskey(d, K("script"))
@@ -135,14 +171,14 @@ julia> d = Dict(:x => 3)Dict{Symbol,Int64} with 1 entry:
135171
136172
julia> @tag!(d) # running from a script or inline evaluation of Juno
137173
Dict{Symbol,Any} with 3 entries:
138-
:commit => "618b72bc0936404ab6a4dd8d15385868b8299d68"
174+
:gitcommit => "618b72bc0936404ab6a4dd8d15385868b8299d68"
139175
:script => "test\\stools_tests.jl#10"
140176
:x => 3
141177
```
142178
"""
143-
macro tag!(d, gitpath = projectdir())
179+
macro tag!(d, gitpath = projectdir(), storepatch = true)
144180
s = QuoteNode(__source__)
145-
:(tag!($(esc(d)), $(esc(gitpath)), $s))
181+
:(tag!($(esc(d)), $(esc(gitpath)), $(esc(storepatch)), $s))
146182
end
147183

148184
"""

test/savefiles_tests.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ end
2121
t = f(simulation)
2222
tagsave(savename(simulation, "bson"), t, findproject())
2323
file = load(savename(simulation, "bson"))
24-
@test "commit" keys(file)
25-
@test file["commit"] |> typeof == String
24+
@test "gitcommit" keys(file)
25+
@test file["gitcommit"] |> typeof == String
2626
rm(savename(simulation, "bson"))
2727

2828
t = f(simulation)
2929
@tagsave(savename(simulation, "bson"), t, false, findproject())
3030
file = load(savename(simulation, "bson"))
31-
@test "commit" keys(file)
32-
@test file["commit"] |> typeof == String
31+
@test "gitcommit" keys(file)
32+
@test file["gitcommit"] |> typeof == String
3333
@test "script" keys(file)
3434
@test file["script"] |> typeof == String
3535
@test file["script"] == joinpath("test", "savefiles_tests.jl#29")

test/stools_tests.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ d2 = Dict("x" => 3, "y" => 4)
1414
for d in (d1, d2)
1515
d = tag!(d, dirname(@__DIR__))
1616

17-
@test haskey(d, keytype(d)(:commit))
18-
@test d[keytype(d)(:commit)] |> typeof <: String
17+
@test haskey(d, keytype(d)(:gitcommit))
18+
@test d[keytype(d)(:gitcommit)] |> typeof <: String
1919
end
2020

2121
# @tag!
2222
for d in (d1, d2)
2323
d = @tag!(d, @__DIR__)
24-
@test !haskey(d, keytype(d)(:commit))
24+
@test !haskey(d, keytype(d)(:gitcommit))
2525

2626
d = @tag!(d, dirname(@__DIR__))
27-
@test d[keytype(d)(:commit)] |> typeof <: String
27+
@test d[keytype(d)(:gitcommit)] |> typeof <: String
2828
@test d[keytype(d)(:script)][1:4] == "test"
2929
end
3030

0 commit comments

Comments
 (0)