git-ged 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +26 -0
- data/LICENSE.grit +26 -0
- data/README.md +84 -0
- data/Rakefile +149 -0
- data/TODO +42 -0
- data/bin/git-ged +3 -0
- data/git-ged.gemspec +51 -0
- data/layout.txt +205 -0
- data/lib/git-ged.rb +53 -0
- data/lib/git-ged/cli.rb +51 -0
- data/lib/git-ged/init.rb +12 -0
- data/lib/git-ged/repo.rb +0 -0
- metadata +97 -0
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2011, John Sumsion
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name of the author nor the names of its contributors
|
13
|
+
may be used to endorse or promote products derived from this software
|
14
|
+
without specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
19
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
20
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
21
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
22
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
23
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
24
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
25
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
26
|
+
POSSIBILITY OF SUCH DAMAGE.
|
data/LICENSE.grit
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
NOTE: Much of the structure of this rubygem, including the README was copied
|
2
|
+
shamelessly from the grit gem, which seemed to be a very good example.
|
3
|
+
Including this license here is an attempt to give proper attribution.
|
4
|
+
|
5
|
+
(The MIT License)
|
6
|
+
|
7
|
+
Copyright (c) 2007-2009 Tom Preston-Werner
|
8
|
+
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
a copy of this software and associated documentation files (the
|
11
|
+
'Software'), to deal in the Software without restriction, including
|
12
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
the following conditions:
|
16
|
+
|
17
|
+
The above copyright notice and this permission notice shall be
|
18
|
+
included in all copies or substantial portions of the Software.
|
19
|
+
|
20
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
23
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
24
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
25
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
26
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
git-ged
|
2
|
+
=======
|
3
|
+
|
4
|
+
* Status: experimental
|
5
|
+
* Author: John Sumsion
|
6
|
+
* Inspiration: Git, Tim Shadel
|
7
|
+
|
8
|
+
GEDCOM plugin for Git. As a `git` subcommand, git-ged lets you import and
|
9
|
+
manage GEDCOM files in a versioned, shareable way in a Git repository.
|
10
|
+
|
11
|
+
It is also possible to attach to other repositories and fetch related
|
12
|
+
genealogy from others who have imported it into their own repository.
|
13
|
+
|
14
|
+
As a library, git-ged lets you write programs that communicate genealogical
|
15
|
+
data in the git-ged repository layout.
|
16
|
+
|
17
|
+
As a repository implementation, git-ged also defines a repository
|
18
|
+
specification that, if adhered to by alternate implementations will render
|
19
|
+
all implementations data-compatible with each other.
|
20
|
+
|
21
|
+
The genealogical data format for persons and relationships is yet to be
|
22
|
+
decided. The first cut will be one that is largely one-to-one compatible
|
23
|
+
with GEDCOM 5.5. I fully expect to change the format before this
|
24
|
+
solidifies, and I'll use whatever is the commonly-accepted format.
|
25
|
+
|
26
|
+
|
27
|
+
## Requirements
|
28
|
+
|
29
|
+
* git (http://git-scm.com) tested with 1.7.8
|
30
|
+
* grit gem (https://github.com/mojombo/grit) tested with 2.4.1
|
31
|
+
|
32
|
+
|
33
|
+
## Install
|
34
|
+
|
35
|
+
Easiest install is via RubyGems:
|
36
|
+
|
37
|
+
$ gem install git-ged
|
38
|
+
|
39
|
+
|
40
|
+
## Source
|
41
|
+
|
42
|
+
Git-ged's Git repo is available on GitHub, which can be browsed at:
|
43
|
+
|
44
|
+
https://github.com/jsumsiong/git-ged
|
45
|
+
|
46
|
+
and cloned with:
|
47
|
+
|
48
|
+
git clone https://github.com/jsumsiong/git-ged.git
|
49
|
+
|
50
|
+
|
51
|
+
### Development
|
52
|
+
|
53
|
+
You will need these gems to get tests to pass:
|
54
|
+
|
55
|
+
* mocha
|
56
|
+
|
57
|
+
|
58
|
+
### Contributing
|
59
|
+
|
60
|
+
If you'd like to hack on git-ged, follow these instructions. To get all of the
|
61
|
+
dependencies, install the gem first.
|
62
|
+
|
63
|
+
1. Fork the project to your own account
|
64
|
+
1. Clone down your fork
|
65
|
+
1. Create a thoughtfully named topic branch to contain your change
|
66
|
+
1. Hack away
|
67
|
+
1. Add tests and make sure everything still passes by running `rake`
|
68
|
+
1. If you are adding new functionality, document it in README.md
|
69
|
+
1. Do not change the version number, I will do that on my end
|
70
|
+
1. If necessary, rebase your commits into logical chunks, without errors
|
71
|
+
1. Push the branch up to GitHub
|
72
|
+
1. Send a pull request for your branch
|
73
|
+
|
74
|
+
|
75
|
+
## Usage
|
76
|
+
|
77
|
+
TODO
|
78
|
+
|
79
|
+
Copyright
|
80
|
+
---------
|
81
|
+
|
82
|
+
Copyright (c) 2011 John Sumsion. See LICENSE for details.
|
83
|
+
|
84
|
+
Portions Copyright (c) 2010 Tom Preston-Warner. See LICENSE.grit for details. Thanks to the github folks for the inspiring Grit gem.
|
data/Rakefile
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/testtask'
|
49
|
+
Rake::TestTask.new(:test) do |test|
|
50
|
+
test.libs << 'lib' << 'test' << '.'
|
51
|
+
test.pattern = 'test/**/test_*.rb'
|
52
|
+
test.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Generate RCov test coverage and open in your browser"
|
56
|
+
task :coverage do
|
57
|
+
require 'rcov'
|
58
|
+
sh "rm -fr coverage"
|
59
|
+
sh "rcov test/test_*.rb"
|
60
|
+
sh "open coverage/index.html"
|
61
|
+
end
|
62
|
+
|
63
|
+
require 'rdoc/task'
|
64
|
+
RDoc::Task.new do |rdoc|
|
65
|
+
rdoc.rdoc_dir = 'rdoc'
|
66
|
+
rdoc.title = "#{name} #{version}"
|
67
|
+
rdoc.rdoc_files.include('README*')
|
68
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "Open an irb session preloaded with this library"
|
72
|
+
task :console do
|
73
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
74
|
+
end
|
75
|
+
|
76
|
+
#############################################################################
|
77
|
+
#
|
78
|
+
# Custom tasks (add your own tasks here)
|
79
|
+
#
|
80
|
+
#############################################################################
|
81
|
+
|
82
|
+
desc "Upload site to Rubyforge"
|
83
|
+
task :site do
|
84
|
+
sh "scp -r doc/* jdsumsion@git-ged.rubyforge.org:/var/www/gforge-projects/git-ged"
|
85
|
+
end
|
86
|
+
|
87
|
+
#############################################################################
|
88
|
+
#
|
89
|
+
# Packaging tasks
|
90
|
+
#
|
91
|
+
#############################################################################
|
92
|
+
|
93
|
+
task :release => :build do
|
94
|
+
unless `git branch` =~ /^\* master$/
|
95
|
+
puts "You must be on the master branch to release!"
|
96
|
+
exit!
|
97
|
+
end
|
98
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
99
|
+
sh "git tag v#{version}"
|
100
|
+
sh "git push origin master"
|
101
|
+
sh "git push origin v#{version}"
|
102
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
103
|
+
end
|
104
|
+
|
105
|
+
task :build => :gemspec do
|
106
|
+
sh "mkdir -p pkg"
|
107
|
+
sh "gem build #{gemspec_file}"
|
108
|
+
sh "mv #{gem_file} pkg"
|
109
|
+
end
|
110
|
+
|
111
|
+
task :gemspec => :validate do
|
112
|
+
# read spec file and split out manifest section
|
113
|
+
spec = File.read(gemspec_file)
|
114
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
115
|
+
|
116
|
+
# replace name version and date
|
117
|
+
replace_header(head, :name)
|
118
|
+
replace_header(head, :version)
|
119
|
+
replace_header(head, :date)
|
120
|
+
#comment this out if your rubyforge_project has a different name
|
121
|
+
replace_header(head, :rubyforge_project)
|
122
|
+
|
123
|
+
# determine file list from git ls-files
|
124
|
+
files = `git ls-files`.
|
125
|
+
split("\n").
|
126
|
+
sort.
|
127
|
+
reject { |file| file =~ /^\./ }.
|
128
|
+
reject { |file| file =~ /^(rdoc|pkg|test|experiments)/ }.
|
129
|
+
map { |file| " #{file}" }.
|
130
|
+
join("\n")
|
131
|
+
|
132
|
+
# piece file back together and write
|
133
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
134
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
135
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
136
|
+
puts "Updated #{gemspec_file}"
|
137
|
+
end
|
138
|
+
|
139
|
+
task :validate do
|
140
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
141
|
+
unless libfiles.empty?
|
142
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
143
|
+
exit!
|
144
|
+
end
|
145
|
+
unless Dir['VERSION*'].empty?
|
146
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
147
|
+
exit!
|
148
|
+
end
|
149
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
Structure
|
2
|
+
- repo layout
|
3
|
+
- library
|
4
|
+
- commands
|
5
|
+
|
6
|
+
Bootstrapping
|
7
|
+
- integrate OptionParser (stdlib) and subcommand (gem) into git-ged commands
|
8
|
+
- patch gem-man to read README{,.md} in lieu of any explicit manpage (gem man rib)
|
9
|
+
|
10
|
+
- create licenses subdir with data licenses by short-name
|
11
|
+
- create templates area that refs/heads/master can be populated from during "git ged init"
|
12
|
+
- create git-ged command that invokes git-ged-*
|
13
|
+
- create lib/git-ged/init.rb that invokes git init + template population (normal or bare)
|
14
|
+
- create lib/git-ged/ingest.rb that copies a GEDCOM in and assigns permaname (refs/local/gedcoms)
|
15
|
+
- ingest one of Hannah's small GEDCOMs
|
16
|
+
- ingest one of Hannah's full GEDCOMs
|
17
|
+
- get rib-git-ged plugin working
|
18
|
+
|
19
|
+
- create living filter pipe for filtering GEDCOMs
|
20
|
+
- create lib/git-ged/import.rb that copies a GEDCOM from refs/{local=>heads}/gedcoms with living filter
|
21
|
+
- create person/family permaname generation strategies (_UUID or name/birth/death, parent permanames)
|
22
|
+
- create entity permaname+state link logic
|
23
|
+
- create lib/git-ged/import-person.rb that copies refs/heads/{gedcoms=>persons} (with living filter)
|
24
|
+
- create lib/git-ged/import-family.rb that copies refs/heads/{gedcoms=>families} (with living filter)
|
25
|
+
- import one of Hannah's GEDCOMs
|
26
|
+
|
27
|
+
- populate person/family => gedcom permaname+state links
|
28
|
+
- create ingest logic for dealing with the second ingest/import cycle on the same GEDCOM
|
29
|
+
- pick default person/family permaname generation logic based on whether the _UUIDs match up
|
30
|
+
- create local/gedcoms history based on
|
31
|
+
|
32
|
+
|
33
|
+
GUI:
|
34
|
+
shoes
|
35
|
+
EXE:
|
36
|
+
ocra
|
37
|
+
shoes (for GUIs)
|
38
|
+
Tools:
|
39
|
+
differ (ruby) for string diffs, edit distance style
|
40
|
+
datadiff (perl) annotated diff
|
41
|
+
datadiff (python) patch production
|
42
|
+
|
data/bin/git-ged
ADDED
data/git-ged.gemspec
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
s.rubygems_version = '1.3.5'
|
5
|
+
|
6
|
+
$:.unshift(File.dirname(__FILE__))
|
7
|
+
require 'lib/git-ged'
|
8
|
+
|
9
|
+
s.name = 'git-ged'
|
10
|
+
s.version = GitGed::VERSION
|
11
|
+
s.date = '2012-01-05'
|
12
|
+
#s.rubyforge_project = 'git-ged'
|
13
|
+
|
14
|
+
s.summary = "GEDCOM plugin for Git"
|
15
|
+
s.description = "git-ged is a Ruby toolset for managing genealogical data (GEDCOM) inside a Git repository."
|
16
|
+
|
17
|
+
s.authors = ["John Sumsion"]
|
18
|
+
s.email = 'jdsumsion@gmail.com'
|
19
|
+
s.homepage = 'http://github.com/jdsumsion/git-ged'
|
20
|
+
|
21
|
+
s.require_paths = %w[lib]
|
22
|
+
|
23
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
24
|
+
s.extra_rdoc_files = %w[README.md LICENSE LICENSE.grit TODO layout.txt]
|
25
|
+
|
26
|
+
s.add_dependency('grit', "~> 2.4.1")
|
27
|
+
s.add_dependency('subcommand', "~> 1.0.6")
|
28
|
+
|
29
|
+
s.add_development_dependency('mocha')
|
30
|
+
|
31
|
+
s.executables << 'git-ged'
|
32
|
+
|
33
|
+
# = MANIFEST =
|
34
|
+
s.files = %w[
|
35
|
+
LICENSE
|
36
|
+
LICENSE.grit
|
37
|
+
README.md
|
38
|
+
Rakefile
|
39
|
+
TODO
|
40
|
+
bin/git-ged
|
41
|
+
git-ged.gemspec
|
42
|
+
layout.txt
|
43
|
+
lib/git-ged.rb
|
44
|
+
lib/git-ged/cli.rb
|
45
|
+
lib/git-ged/init.rb
|
46
|
+
lib/git-ged/repo.rb
|
47
|
+
]
|
48
|
+
# = MANIFEST =
|
49
|
+
|
50
|
+
s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
|
51
|
+
end
|
data/layout.txt
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
GIT-GED CONCEPTS
|
2
|
+
================
|
3
|
+
|
4
|
+
1) Git-ged commands
|
5
|
+
[repo-level]
|
6
|
+
- Init: populates refs/heads/{master,content,intent} with README, LICENSE_DEFAULT, etc. as specified by user
|
7
|
+
- Clone: same as git clone
|
8
|
+
- Attach: same as git remote add, except that it allows the user to define the set of permanames of interest in the remote repo
|
9
|
+
- Fetch: same as git fetch, except that only permanames that the user currently has are fetched (along with refs/heads/master)
|
10
|
+
- Push: same as git push, except that only permanames that the remote repo has are updated
|
11
|
+
[entity-state]
|
12
|
+
- Intent: captures one-line user intent (separate from commit message)
|
13
|
+
- Ingest: populates refs/local/gedcoms/{permaname(s)} with a raw gedcom, adding new gedN files for permaname collisions
|
14
|
+
- Filter: performs living filter and populates refs/heads/gedcoms/{permaname}
|
15
|
+
- Import: creates any new persons/families and records import flag & new person/family permaname+state links in gedX.IMPORT
|
16
|
+
[interactive-use]
|
17
|
+
- Workspace: (subcommands: checkout, update, reset)
|
18
|
+
- checkout: grabs every specified person/family/gedcom permaname and puts it under the path repo/[persons|families|gedcoms]/{permaname} in a transient commit
|
19
|
+
- update: grabs the head of every specified person/family/gedcom permaname and puts it under a new refs/heads/workspace commit
|
20
|
+
- reset: warns if un-git-ged-committed refs/heads/workspace history, then nukes it and recreates a refs/heads/workspace commit with the same permanames as it had before
|
21
|
+
- Resolve: deals with multiple-record histories in a smart way (gedcoms/persons/families)
|
22
|
+
- Commit: creates commits on every separate permaname that has changed, using current intent as default commit subject plus details of all entities that changed
|
23
|
+
|
24
|
+
2) Data Licenses
|
25
|
+
- Data license defaults to Creative Commons Attribution-ShareAlike 3.0 Unported
|
26
|
+
- Users can update license at repo/gedcom/record level
|
27
|
+
- Other common license options are easily specifiable
|
28
|
+
- Other Creative Commons options: CC-BY-3.0, CC-BY-NC-3.0, CC-BY-NC-SA-3.0, CC0-1.0
|
29
|
+
- Open Data Commons options: ODC-PDDL-1.0, ODC-BY-1.0, ODC-ODBL-1.0
|
30
|
+
|
31
|
+
3) Gedcom
|
32
|
+
- all ingested gedcoms are stored verbatim under a "refs/local/gedcoms/{permaname}" ref
|
33
|
+
- all imported, living-filtered gedcoms are stored under a "refs/heads/gedcoms/{permaname}" ref
|
34
|
+
- there are multiple permanames for a gedcom:
|
35
|
+
- primary permaname for gedcom is the sha256 hash of "FILE & SUBM name[/email]"
|
36
|
+
- alternate permaname for gedcom is the sha256 hash of "FILE & DATE"
|
37
|
+
- alternate permaname for gedcom is the sha256 hash of "source path & SUBM name"
|
38
|
+
- alternate permaname for gedcom is the sha256 hash of all person _UUIDs, sorted alphanumerically
|
39
|
+
- keep your email, paths & filenames consistent to maximize permaname collisions
|
40
|
+
- copies the repo's LICENSE_DEFAULT unless overridden by the user at ingest time
|
41
|
+
|
42
|
+
4) Person
|
43
|
+
- all imported persons are stored in a standard JSON format under a "refs/heads/persons/{permaname}" refs
|
44
|
+
- there are multiple permanames for a person:
|
45
|
+
- one is designated as primary, all permanames are annotated internally to the person by a versioned hash function (for easy recomputation)
|
46
|
+
- primary permaname for person defaults to the sha256 hash of the gedcom UID + sorted 'not-same-as' list
|
47
|
+
- alternate permaname for person is the sha256 hash of "<displayname>[ birthdate][ deathdate][ sorted 'not-same-as' list]" (can be designated as primary)
|
48
|
+
- alternate permaname for person is the sha256 hash of "<displayname> child of <permaname>" (for each parent, if there is no birth/death)
|
49
|
+
- other alternate permanames are definable, as long as the context-free code for computing them is included under refs/heads/code
|
50
|
+
- permanames based on "displayname" state should not change when the person's name is updated, they are designed to produce collisions based on import data
|
51
|
+
- although a person's primary permaname should remain largely constant, it can change for reasons of disambiguation (addition of a 'not-same-as' link)
|
52
|
+
- proper protection of living data
|
53
|
+
- all imported persons must be deceased and publicly-viewable, or the record will initially exist under the "refs/local/persons/..." refspace
|
54
|
+
- import is responsible for initially segmenting living vs. deceased, but after import the app is responsible for calculating living after edit and moving the record if necessary
|
55
|
+
- the same living check & segmentation logic that import uses will be available on-demand for apps to use after edit
|
56
|
+
- when a deceased record is made living, the deceased record's permaname gets a new commit that marks it as no-longer-visible (so that on subsequent fetch, other repositories can delete their records)
|
57
|
+
- includes optional "supersedes", "superseded-by", "derived-from", "same-as", and "not-same-as" link attributes
|
58
|
+
- see
|
59
|
+
- the "derived-from", "supersedes", and "superseded-by" link attributes allow for proper history-aware
|
60
|
+
linkage across permanames for tracing merge or other complicated person record derivation
|
61
|
+
- the "same-as" attribute is a loose identity link to an alternate history of a person
|
62
|
+
determined to be the same historical person, it is a hint that a merge or other record derivation may be useful in the future
|
63
|
+
- if the "not-same-as" attribute refers to a disconnected history of a *different* person under the same permaname,
|
64
|
+
it is best to change the permaname of each person this attribute is added to
|
65
|
+
- person merge is often not needed if the persons originated unchanged from the same gedcom
|
66
|
+
- person merge may be a "comes before/comes after" decision at import time
|
67
|
+
- person merge decisions can be deferred by storing two person entities under the same permaname
|
68
|
+
and letting the user make the before/after decision at a later time
|
69
|
+
- multiple persons can be stored under the same permaname under 'entity1'..'entityN' blob names
|
70
|
+
- copies the gedcom LICENSE if imported, copies the repo's LICENSE_DEFAULT if a newly-created person
|
71
|
+
|
72
|
+
5) Family
|
73
|
+
- direct represenation of the GEDCOM family concept
|
74
|
+
- all imported families are stored in a standard JSON format under a "refs/heads/families/{permaname}" refs
|
75
|
+
- there are multiple permanames for a family:
|
76
|
+
- one is designated as primary, all permanames are annotated internally to the family by a versioned hash function (for easy recomputation)
|
77
|
+
- primary permaname for family defaults to the sha256 hash of the parents' primary permanames in alphanumeric order
|
78
|
+
- alternate permaname for family is the sha256 hash of "<displayname>[ displayname][ marriage date]" (if there is marriage information, displaynames ordered by primary permaname alphanumeric ordering of parents)
|
79
|
+
- other alternate permanames are definable, as long as the context-free code for computing them is included under refs/heads/code
|
80
|
+
- permanames based on "displayname" state should not change when the person's name is updated, they are designed to produce collisions based on import data
|
81
|
+
- primary permaname should change if the parents' permanames are modified, or if a parent is replaced with a different person
|
82
|
+
- includes person links for each person in each family position
|
83
|
+
- includes optional "supersedes", "superseded-by", "derived-from", "same-as", and "not-same-as" link attributes (like person)
|
84
|
+
- includes optional "prior-family" link attribute that tracks temporal household dissolution & recomposition
|
85
|
+
- family merge is often not needed if the family originated unchanged from the same gedcom
|
86
|
+
- family merge can be a "comes before/comes after" decision at import time, but is not as likely as person to fit this workflow
|
87
|
+
- family merge decisions can be deferred by storing two family entities under the same permaname
|
88
|
+
and letting the user make the before/after decision at a later time
|
89
|
+
- multiple families can be stored under the same permaname under 'entity1'..'entityN' blob names
|
90
|
+
- copies the gedcom LICENSE if imported, copies the repo's LICENSE_DEFAULT if a newly-created record
|
91
|
+
|
92
|
+
6) Link Attribute
|
93
|
+
- link attributes always refer to BOTH primary permaname & state
|
94
|
+
- link attributes are encoded similar to XFN to start
|
95
|
+
|
96
|
+
7) Merge Strategies
|
97
|
+
- merge comes in at least 4 flavors:
|
98
|
+
a) [import] alternate record storage + history linkage (recommended for automated import processes)
|
99
|
+
b) [post-import] "comes before/comes after" record replacement strategy for dealing with record reconciliation
|
100
|
+
c) [post-import] "pick correct values" record reconciliation strategy for dealing with partial truth from multiple sources (sets "derived-from" attribute(s))
|
101
|
+
d) [post-import] "disambiguation" record reconciliation strategy for separating records that are NOT the same person (sets "not-same-as" attribute(s))
|
102
|
+
- of course anyone can do anything they please to their records, but these strategies seem to deserve automation support
|
103
|
+
|
104
|
+
8) Record deriviation tracing
|
105
|
+
- each of the following link attributes refers to another record via permaname+state reference
|
106
|
+
- "supersedes" indicates that a person (or family) is a full, identical replacement for another (used to be able to react to remote merges)
|
107
|
+
- "superseded-by" indicates that a static, not-to-be-further-edited person (or family) is left behind after having been merged into the canonical record (used to detect conflicts between local edits & remote merge or vice versa)
|
108
|
+
- "derived-from" indicates a clone-and-mutate to disambiguate a fully-merged record that represents two distinct historical persons or families (used to document manual record reconstruction efforts)
|
109
|
+
- "same-as" indicates a more-tentative-than-merge attempt at establishing identity to allow for disparate efforts to proceed before attempting a full merge
|
110
|
+
- "not-same-as" indicates a definitive statement that one person (or family) is NOT the same as another (used when extracting a half-merged entity that collided on a permaname)
|
111
|
+
- "prior-family" indicates that a family has many of the same members of a prior family, but temporal household dissolution & recomposition require two different families to be tracked (allows for "current family" computations based on a directed graph of prior-family edges)
|
112
|
+
|
113
|
+
9) Deletion of Records
|
114
|
+
- person/family/gedcom delete comes in three flavors:
|
115
|
+
- "deref": I no longer care to track or maintain this person, if anyone has this person cloned, let them continue to maintain the record
|
116
|
+
- "hide": I want the record gone on any repo that follows mine, if they fetch from me, MAKE their record go away, dead-to-living transition uses this mechanism
|
117
|
+
- "delete": I want the record gone on any repo that follows mine, if they fetch from me, SUGGEST that their record go away
|
118
|
+
- "hide" and "delete" stubs hang around for a longish-but-limited amount of time, and there is a mechanism that automatically cleans them up every so often
|
119
|
+
|
120
|
+
|
121
|
+
GIT-GED REFS LAYOUT
|
122
|
+
===================
|
123
|
+
|
124
|
+
refs/heads/*:
|
125
|
+
- stuff that can be cloned/forked
|
126
|
+
refs/local/*:
|
127
|
+
- dispensible stuff that is used for local import actions (not needed for collaboration)
|
128
|
+
- hidden stuff that should not be published on a clone/fork
|
129
|
+
|
130
|
+
refs/heads/master (fetchable, but non-mergeable):
|
131
|
+
- README: simple documentation of git-ged, with pointer to software to parse/use
|
132
|
+
|
133
|
+
refs/heads/content (fetchable, but non-mergeable):
|
134
|
+
- META: last version of git-ged that wrote
|
135
|
+
- INTENT: link to last intent
|
136
|
+
- ROOTS: links to various person roots
|
137
|
+
- ENTITIES: list of gedcom/person/family permaname+state links, may end up needing to be sharded for very large repos
|
138
|
+
- LICENSE_DEFAULT: default license for any new records added or imported into to this repository, defaults to Creative Commons Share-alike
|
139
|
+
- [non-versioned] CHANGELOG: contains up to last 100 edits performed, updated by git-ged commit
|
140
|
+
|
141
|
+
refs/heads/intent (fetchable, but non-mergeable):
|
142
|
+
- INTENT: stores the user's identity, single-line intent message, and date in the tree itself to capture "why"
|
143
|
+
- MUST exist: if no intent is explicitly stored, init/import/edit must stub one in that describes the largest-scope action being taken
|
144
|
+
- able to be as fine-grained and meticulous as the user wants to be, while allowing the user
|
145
|
+
the convenience of keeping the same intent over several small actions moving toward a large-time-scale goal
|
146
|
+
- able to generate a feed of intents from here
|
147
|
+
|
148
|
+
refs/heads/workspace (fetchable, but non-mergeable):
|
149
|
+
- non-history-preserving workspace tree for pulling a subset of records into a filesystem for edit
|
150
|
+
|
151
|
+
refs/heads/gedcoms/{permanames}:
|
152
|
+
- ged1: the living-filtered gedcom file renamed to a standard filename
|
153
|
+
- gedN: the living-filtered gedcom file renamed to a standard filename
|
154
|
+
- contains no living data, as a result of "ingest" followed by "import"
|
155
|
+
- gedX.META: link to intent; original file name & path; where/who the file came from; all permanames this gedcom was stored under (by permaname kind)
|
156
|
+
- gedX.LICENSE: license for use of this gedcom as a whole, copied to individual persons/families at import time
|
157
|
+
|
158
|
+
refs/local/gedcoms/{permanames}:
|
159
|
+
- *may* contain living data, populated without filtering by "ingest"
|
160
|
+
- ged1.ged: the raw gedcom file renamed to a standard filename
|
161
|
+
- gedN.ged: the raw gedcom file renamed to a standard filename
|
162
|
+
- pre-existing permanames get forwarded up to the new tree (collisions, yay!)
|
163
|
+
- new permanames get created, non-colliding permanames don't get forwarded (gc'd by some other mechanism)
|
164
|
+
- if the gedcom contains no living data, "import" can delete the refs/local refs
|
165
|
+
- can coexist with refs/heads/gedcoms/{permaname} for a while as documentation of exactly what got imported
|
166
|
+
- fetch/clone/fork does NOT include the original gedcom, just the "post-import" one (because only refs/heads comes along)
|
167
|
+
|
168
|
+
refs/heads/persons/{permanames}:
|
169
|
+
- entity1: the primary person in a standard JSON form
|
170
|
+
- entityN: alternate, not-yet-merged person records
|
171
|
+
- entityX.{format}: the primary person (or an alternate) in an alternate format
|
172
|
+
- entityX.META: link to intent; link(s) to gedcom permanames
|
173
|
+
- entityX.IDENTITY: optional "supersedes", "superseded-by", "derived-from", "same-as", and "not-same-as" link attributes; also any arbitrary XFNs to other permanames
|
174
|
+
- entityX.LICENSE: license for use of this person, copied from gedcom at import time, or from repo's LICENSE_DEFAULT at non-import-creation time
|
175
|
+
- commits on a person can easily be merged/fast-forwarded if history is shared and there are no textual conflicts
|
176
|
+
- commits on two persons of the same permaname that do NOT share history can undergo person merge
|
177
|
+
- merge commits set "supersedes" and "superseded-by" link attributes to allow post-merge traceability
|
178
|
+
- merging of META takes a "most recent intent / union" approach
|
179
|
+
- merging of IDENTITY takes a "union with conflict detection & manual resolution" approach
|
180
|
+
- merging of LICENSE takes the more restrictive license by default
|
181
|
+
|
182
|
+
refs/local/persons/{permanames}:
|
183
|
+
- non-public person record, behaves like person in every other respect
|
184
|
+
- typically a person record should exist under EITHER refs/local OR refs/heads
|
185
|
+
- if both exist, the refs/heads record is used exclusively
|
186
|
+
|
187
|
+
refs/heads/families/{permanames}:
|
188
|
+
- entity1: the primary family in a standard JSON form
|
189
|
+
- entityN: alternate, not-yet-merged family records
|
190
|
+
- entityX.{format}: the primary person (or an alternate) in an alternate format
|
191
|
+
- entityX.META: link to intent; link(s) to gedcom permanames
|
192
|
+
- entityX.IDENTITY: optional "prior-family", "derived-from", "supersedes", "superseded-by", "same-as", and "not-same-as" link attributes; also any arbitrary XFNs to other permanames
|
193
|
+
- entityX.LICENSE: license for use of this person, copied from gedcom at import time, or from repo's LICENSE_DEFAULT at non-import-creation time
|
194
|
+
- commits on a family can easily be merged/fast-forwarded if history is shared and there are no textual conflicts
|
195
|
+
- commits on two families of the same permaname that do NOT share history can undergo family merge
|
196
|
+
- merging of META takes a "most recent intent / union" approach
|
197
|
+
- merging of IDENTITY takes a "union with conflict detection & manual resolution" approach
|
198
|
+
- merging of LICENSE takes the more restrictive license by default
|
199
|
+
|
200
|
+
refs/local/families/{permanames}:
|
201
|
+
- non-public family record, behaves like person in every other respect
|
202
|
+
- typically a family record should exist under EITHER refs/local OR refs/heads
|
203
|
+
- if both exist, the refs/heads record is used exclusively
|
204
|
+
|
205
|
+
|
data/lib/git-ged.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
|
2
|
+
|
3
|
+
# core
|
4
|
+
require 'fileutils'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
# stdlib
|
8
|
+
require 'logger'
|
9
|
+
require 'digest/sha1'
|
10
|
+
|
11
|
+
# third party
|
12
|
+
require 'grit'
|
13
|
+
|
14
|
+
# internal requires
|
15
|
+
|
16
|
+
# common libraries
|
17
|
+
require 'git-ged/repo'
|
18
|
+
|
19
|
+
# git-like repo interaction
|
20
|
+
require 'git-ged/cli'
|
21
|
+
require 'git-ged/init'
|
22
|
+
|
23
|
+
# internal support classes
|
24
|
+
|
25
|
+
module GitGed
|
26
|
+
VERSION = '0.0.2'
|
27
|
+
|
28
|
+
class << self
|
29
|
+
|
30
|
+
def version
|
31
|
+
VERSION
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_accessor :debug
|
35
|
+
|
36
|
+
def grit_debug= onoff
|
37
|
+
Grit.debug = onoff
|
38
|
+
end
|
39
|
+
|
40
|
+
# The standard +logger+ for debugging git-ged calls - this defaults to a plain STDOUT logger
|
41
|
+
attr_accessor :logger
|
42
|
+
|
43
|
+
def log(str)
|
44
|
+
logger.debug { str }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
self.debug = false
|
49
|
+
self.grit_debug = false
|
50
|
+
|
51
|
+
@logger ||= ::Logger.new(STDOUT)
|
52
|
+
|
53
|
+
end
|
data/lib/git-ged/cli.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'git-ged'
|
4
|
+
require 'optparse'
|
5
|
+
require 'subcommand'
|
6
|
+
|
7
|
+
module GitGed
|
8
|
+
class CLI
|
9
|
+
|
10
|
+
include Subcommands
|
11
|
+
|
12
|
+
# patch until subcommand 1.0.7 comes out
|
13
|
+
attr_accessor :appname
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@options = {}
|
17
|
+
|
18
|
+
self.appname = "git ged"
|
19
|
+
global_options do |opts|
|
20
|
+
opts.banner = "Usage: #{appname} [options] [subcommand [options]]"
|
21
|
+
opts.separator ""
|
22
|
+
opts.separator "Global options are:"
|
23
|
+
opts.on("-v", "--[no-]verbose", "Show git-ged & grit debug") do |v|
|
24
|
+
GitGed.debug = v
|
25
|
+
GitGed.grit_debug = v
|
26
|
+
end
|
27
|
+
end
|
28
|
+
add_help_option
|
29
|
+
|
30
|
+
command :init do |opts|
|
31
|
+
opts.banner = "Usage: #{appname} init [-m msg] [repo]"
|
32
|
+
opts.description = "Initializes a new git-ged repo"
|
33
|
+
opts.separator ""
|
34
|
+
opts.separator "Options:"
|
35
|
+
opts.on "-m MESSAGE", "--message MESSAGE" do |msg|
|
36
|
+
@options[:message] = msg
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def run
|
42
|
+
cmd = opt_parse()
|
43
|
+
if cmd
|
44
|
+
Repo.new.send cmd, ARGV, @options
|
45
|
+
else
|
46
|
+
puts global_options { |opts| opts }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/git-ged/init.rb
ADDED
data/lib/git-ged/repo.rb
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git-ged
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- John Sumsion
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: grit
|
16
|
+
requirement: &14236180 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.4.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *14236180
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: subcommand
|
27
|
+
requirement: &14235700 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.6
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *14235700
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: mocha
|
38
|
+
requirement: &14235320 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *14235320
|
47
|
+
description: git-ged is a Ruby toolset for managing genealogical data (GEDCOM) inside
|
48
|
+
a Git repository.
|
49
|
+
email: jdsumsion@gmail.com
|
50
|
+
executables:
|
51
|
+
- git-ged
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files:
|
54
|
+
- README.md
|
55
|
+
- LICENSE
|
56
|
+
- LICENSE.grit
|
57
|
+
- TODO
|
58
|
+
- layout.txt
|
59
|
+
files:
|
60
|
+
- LICENSE
|
61
|
+
- LICENSE.grit
|
62
|
+
- README.md
|
63
|
+
- Rakefile
|
64
|
+
- TODO
|
65
|
+
- bin/git-ged
|
66
|
+
- git-ged.gemspec
|
67
|
+
- layout.txt
|
68
|
+
- lib/git-ged.rb
|
69
|
+
- lib/git-ged/cli.rb
|
70
|
+
- lib/git-ged/init.rb
|
71
|
+
- lib/git-ged/repo.rb
|
72
|
+
homepage: http://github.com/jdsumsion/git-ged
|
73
|
+
licenses: []
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options:
|
76
|
+
- --charset=UTF-8
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.8.11
|
94
|
+
signing_key:
|
95
|
+
specification_version: 2
|
96
|
+
summary: GEDCOM plugin for Git
|
97
|
+
test_files: []
|