git-ged 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|