diff options
-rw-r--r-- | commands/convert.go | 88 | ||||
-rw-r--r-- | commands/import_jekyll.go | 34 | ||||
-rw-r--r-- | commands/new_site.go | 5 | ||||
-rw-r--r-- | hugolib/page.go | 32 | ||||
-rw-r--r-- | parser/frontmatter.go | 165 | ||||
-rw-r--r-- | parser/frontmatter_test.go | 206 | ||||
-rw-r--r-- | parser/long_text_test.md | 263 | ||||
-rw-r--r-- | parser/page.go | 408 | ||||
-rw-r--r-- | parser/page_test.go | 1 | ||||
-rw-r--r-- | parser/pageparser/item.go | 4 | ||||
-rw-r--r-- | parser/pageparser/pagelexer.go | 35 | ||||
-rw-r--r-- | parser/pageparser/pageparser.go | 8 | ||||
-rw-r--r-- | parser/parse_frontmatter_test.go | 324 | ||||
-rw-r--r-- | tpl/transform/remarshal.go | 14 |
14 files changed, 120 insertions, 1467 deletions
diff --git a/commands/convert.go b/commands/convert.go index 29714301f..b208f6cab 100644 --- a/commands/convert.go +++ b/commands/convert.go @@ -16,6 +16,7 @@ package commands import ( "bytes" "fmt" + "io" "strings" "time" @@ -34,7 +35,6 @@ import ( "path/filepath" - "github.com/spf13/cast" "github.com/spf13/cobra" ) @@ -156,7 +156,7 @@ func (cc *convertCmd) convertAndSavePage(p *hugolib.Page, site *hugolib.Site, ta return nil } - psr, err := pageparser.Parse(file) + pf, err := parseContentFile(file) if err != nil { site.Log.ERROR.Println(errMsg) file.Close() @@ -165,53 +165,24 @@ func (cc *convertCmd) convertAndSavePage(p *hugolib.Page, site *hugolib.Site, ta file.Close() - var sourceFormat, sourceContent []byte - var fromFormat metadecoders.Format - - iter := psr.Iterator() - - walkFn := func(item pageparser.Item) bool { - if sourceFormat != nil { - // The rest is content. - sourceContent = psr.Input()[item.Pos:] - // Done - return false - } else if item.IsFrontMatter() { - fromFormat = metadecoders.FormatFromFrontMatterType(item.Type) - sourceFormat = item.Val - } - return true - - } - - iter.PeekWalk(walkFn) - - metadata, err := metadecoders.UnmarshalToMap(sourceFormat, fromFormat) - if err != nil { - site.Log.ERROR.Println(errMsg) - return err - } - // better handling of dates in formats that don't have support for them - if fromFormat == metadecoders.JSON || fromFormat == metadecoders.YAML || fromFormat == metadecoders.TOML { - newMetadata := cast.ToStringMap(metadata) - for k, v := range newMetadata { + if pf.frontMatterFormat == metadecoders.JSON || pf.frontMatterFormat == metadecoders.YAML || pf.frontMatterFormat == metadecoders.TOML { + for k, v := range pf.frontMatter { switch vv := v.(type) { case time.Time: - newMetadata[k] = vv.Format(time.RFC3339) + pf.frontMatter[k] = vv.Format(time.RFC3339) } } - metadata = newMetadata } var newContent bytes.Buffer - err = parser.InterfaceToFrontMatter2(metadata, targetFormat, &newContent) + err = parser.InterfaceToFrontMatter(pf.frontMatter, targetFormat, &newContent) if err != nil { site.Log.ERROR.Println(errMsg) return err } - newContent.Write(sourceContent) + newContent.Write(pf.content) newFilename := p.Filename() @@ -229,3 +200,48 @@ func (cc *convertCmd) convertAndSavePage(p *hugolib.Page, site *hugolib.Site, ta return nil } + +type parsedFile struct { + frontMatterFormat metadecoders.Format + frontMatterSource []byte + frontMatter map[string]interface{} + + // Everything after Front Matter + content []byte +} + +func parseContentFile(r io.Reader) (parsedFile, error) { + var pf parsedFile + + psr, err := pageparser.Parse(r) + if err != nil { + return pf, err + } + + iter := psr.Iterator() + + walkFn := func(item pageparser.Item) bool { + if pf.frontMatterSource != nil { + // The rest is content. + pf.content = psr.Input()[item.Pos:] + // Done + return false + } else if item.IsFrontMatter() { + pf.frontMatterFormat = metadecoders.FormatFromFrontMatterType(item.Type) + pf.frontMatterSource = item.Val + } + return true + + } + + iter.PeekWalk(walkFn) + + metadata, err := metadecoders.UnmarshalToMap(pf.frontMatterSource, pf.frontMatterFormat) + if err != nil { + return pf, err + } + pf.frontMatter = metadata + + return pf, nil + +} diff --git a/commands/import_jekyll.go b/commands/import_jekyll.go index 2a86840d6..fc3a84027 100644 --- a/commands/import_jekyll.go +++ b/commands/import_jekyll.go @@ -16,6 +16,7 @@ package commands import ( "bytes" "errors" + "fmt" "io" "io/ioutil" "os" @@ -264,7 +265,7 @@ func (i *importCmd) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string] return c } -func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind string, jekyllConfig map[string]interface{}) (err error) { +func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind metadecoders.Format, jekyllConfig map[string]interface{}) (err error) { title := "My New Hugo Site" baseURL := "http://example.org/" @@ -290,15 +291,14 @@ func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind stri "languageCode": "en-us", "disablePathToLower": true, } - kind = parser.FormatSanitize(kind) var buf bytes.Buffer - err = parser.InterfaceToConfig(in, parser.FormatToLeadRune(kind), &buf) + err = parser.InterfaceToConfig(in, kind, &buf) if err != nil { return err } - return helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), &buf, fs) + return helpers.WriteToDisk(filepath.Join(inpath, "config."+string(kind)), &buf, fs) } func copyFile(source string, dest string) error { @@ -447,39 +447,25 @@ func convertJekyllPost(s *hugolib.Site, path, relPath, targetDir string, draft b return err } - psr, err := parser.ReadFrom(bytes.NewReader(contentBytes)) + pf, err := parseContentFile(bytes.NewReader(contentBytes)) if err != nil { jww.ERROR.Println("Parse file error:", path) return err } - metadata, err := psr.Metadata() - if err != nil { - jww.ERROR.Println("Processing file error:", path) - return err - } - - newmetadata, err := convertJekyllMetaData(metadata, postName, postDate, draft) + newmetadata, err := convertJekyllMetaData(pf.frontMatter, postName, postDate, draft) if err != nil { jww.ERROR.Println("Convert metadata error:", path) return err } - jww.TRACE.Println(newmetadata) - content := convertJekyllContent(newmetadata, string(psr.Content())) + content := convertJekyllContent(newmetadata, string(pf.content)) - page, err := s.NewPage(filename) - if err != nil { - jww.ERROR.Println("New page error", filename) - return err + fs := hugofs.Os + if err := helpers.WriteToDisk(targetFile, strings.NewReader(content), fs); err != nil { + return fmt.Errorf("Failed to save file %q:", filename) } - page.SetSourceContent([]byte(content)) - page.SetSourceMetaData(newmetadata, parser.FormatToLeadRune("yaml")) - page.SaveSourceAs(targetFile) - - jww.TRACE.Println("Target file:", targetFile) - return nil } diff --git a/commands/new_site.go b/commands/new_site.go index 2233157ed..114ee82f6 100644 --- a/commands/new_site.go +++ b/commands/new_site.go @@ -19,6 +19,8 @@ import ( "path/filepath" "strings" + "github.com/gohugoio/hugo/parser/metadecoders" + _errors "github.com/pkg/errors" "github.com/gohugoio/hugo/create" @@ -131,10 +133,9 @@ func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) { "title": "My New Hugo Site", "languageCode": "en-us", } - kind = parser.FormatSanitize(kind) var buf bytes.Buffer - err = parser.InterfaceToConfig(in, parser.FormatToLeadRune(kind), &buf) + err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(kind), &buf) if err != nil { return err } diff --git a/hugolib/page.go b/hugolib/page.go index 2db0fb5d4..d13dfb246 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -36,7 +36,6 @@ import ( "github.com/gohugoio/hugo/resource" "github.com/gohugoio/hugo/output" - "github.com/gohugoio/hugo/parser" "github.com/mitchellh/mapstructure" "html/template" @@ -485,6 +484,7 @@ func (p *Page) MediaType() media.Type { return media.OctetType } +// TODO(bep) 2errors remove type Source struct { Frontmatter []byte Content []byte @@ -1725,36 +1725,6 @@ func (p *Page) SetSourceContent(content []byte) { p.Source.Content = content } -func (p *Page) SetSourceMetaData(in interface{}, mark rune) (err error) { - // See https://github.com/gohugoio/hugo/issues/2458 - defer func() { - if r := recover(); r != nil { - var ok bool - err, ok = r.(error) - if !ok { - err = fmt.Errorf("error from marshal: %v", r) - } - } - }() - - buf := bp.GetBuffer() - defer bp.PutBuffer(buf) - - err = parser.InterfaceToFrontMatter(in, mark, buf) - if err != nil { - return - } - - _, err = buf.WriteRune('\n') - if err != nil { - return - } - - p.Source.Frontmatter = buf.Bytes() - - return -} - func (p *Page) SafeSaveSourceAs(path string) error { return p.saveSourceAs(path, true) } diff --git a/parser/frontmatter.go b/parser/frontmatter.go index a42db0cca..ab1bc4d55 100644 --- a/parser/frontmatter.go +++ b/parser/frontmatter.go @@ -13,14 +13,10 @@ package parser -// TODO(bep) archetype remove unused from this package. - import ( - "bytes" "encoding/json" "errors" "io" - "strings" "github.com/gohugoio/hugo/parser/metadecoders" @@ -29,53 +25,12 @@ import ( "gopkg.in/yaml.v2" ) -// FrontmatterType represents a type of frontmatter. -type FrontmatterType struct { - // Parse decodes content into a Go interface. - Parse func([]byte) (map[string]interface{}, error) - - markstart, markend []byte // starting and ending delimiters - includeMark bool // include start and end mark in output -} - -// InterfaceToConfig encodes a given input based upon the mark and writes to w. -func InterfaceToConfig(in interface{}, mark rune, w io.Writer) error { - if in == nil { - return errors.New("input was nil") - } - - switch mark { - case rune(YAMLLead[0]): - b, err := yaml.Marshal(in) - if err != nil { - return err - } - - _, err = w.Write(b) - return err - - case rune(TOMLLead[0]): - return toml.NewEncoder(w).Encode(in) - case rune(JSONLead[0]): - b, err := json.MarshalIndent(in, "", " ") - if err != nil { - return err - } - - _, err = w.Write(b) - if err != nil { - return err - } - - _, err = w.Write([]byte{'\n'}) - return err - - default: - return errors.New("Unsupported Format provided") - } -} +const ( + yamlDelimLf = "---\n" + tomlDelimLf = "+++\n" +) -func InterfaceToConfig2(in interface{}, format metadecoders.Format, w io.Writer) error { +func InterfaceToConfig(in interface{}, format metadecoders.Format, w io.Writer) error { if in == nil { return errors.New("input was nil") } @@ -111,136 +66,42 @@ func InterfaceToConfig2(in interface{}, format metadecoders.Format, w io.Writer) } } -func InterfaceToFrontMatter2(in interface{}, format metadecoders.Format, w io.Writer) error { +func InterfaceToFrontMatter(in interface{}, format metadecoders.Format, w io.Writer) error { if in == nil { return errors.New("input was nil") } switch format { case metadecoders.YAML: - _, err := w.Write([]byte(YAMLDelimUnix)) + _, err := w.Write([]byte(yamlDelimLf)) if err != nil { return err } - err = InterfaceToConfig2(in, format, w) + err = InterfaceToConfig(in, format, w) if err != nil { return err } - _, err = w.Write([]byte(YAMLDelimUnix)) + _, err = w.Write([]byte(yamlDelimLf)) return err case metadecoders.TOML: - _, err := w.Write([]byte(TOMLDelimUnix)) + _, err := w.Write([]byte(tomlDelimLf)) if err != nil { return err } - err = InterfaceToConfig2(in, format, w) + err = InterfaceToConfig(in, format, w) if err != nil { return err } - _, err = w.Write([]byte("\n" + TOMLDelimUnix)) + _, err = w.Write([]byte("\n" + tomlDelimLf)) return err default: - return InterfaceToConfig2(in, format, w) - } -} - -// InterfaceToFrontMatter encodes a given input into a frontmatter -// representation based upon the mark with the appropriate front matter delimiters -// surrounding the output, which is written to w. -func InterfaceToFrontMatter(in interface{}, mark rune, w io.Writer) error { - if in == nil { - return errors.New("input was nil") + return InterfaceToConfig(in, format, w) } - - switch mark { - case rune(YAMLLead[0]): - _, err := w.Write([]byte(YAMLDelimUnix)) - if err != nil { - return err - } - - err = InterfaceToConfig(in, mark, w) - if err != nil { - return err - } - - _, err = w.Write([]byte(YAMLDelimUnix)) - return err - - case rune(TOMLLead[0]): - _, err := w.Write([]byte(TOMLDelimUnix)) - if err != nil { - return err - } - - err = InterfaceToConfig(in, mark, w) - - if err != nil { - return err - } - - _, err = w.Write([]byte("\n" + TOMLDelimUnix)) - return err - - default: - return InterfaceToConfig(in, mark, w) - } -} - -// FormatToLeadRune takes a given format kind and return the leading front -// matter delimiter. -func FormatToLeadRune(kind string) rune { - switch FormatSanitize(kind) { - case "yaml": - return rune([]byte(YAMLLead)[0]) - case "json": - return rune([]byte(JSONLead)[0]) - case "org": - return '#' - default: - return rune([]byte(TOMLLead)[0]) - } -} - -// FormatSanitize returns the canonical format name for a given kind. -// -// TODO(bep) move to helpers -func FormatSanitize(kind string) string { - switch strings.ToLower(kind) { - case "yaml", "yml": - return "yaml" - case "toml", "tml": - return "toml" - case "json", "js": - return "json" - case "org": - return kind - default: - return "toml" - } -} - -// removeTOMLIdentifier removes, if necessary, beginning and ending TOML -// frontmatter delimiters from a byte slice. -func removeTOMLIdentifier(datum []byte) []byte { - ld := len(datum) - if ld < 8 { - return datum - } - - b := bytes.TrimPrefix(datum, []byte(TOMLDelim)) - if ld-len(b) != 3 { - // No TOML prefix trimmed, so bail out - return datum - } - - b = bytes.Trim(b, "\r\n") - return bytes.TrimSuffix(b, []byte(TOMLDelim)) } diff --git a/parser/frontmatter_test.go b/parser/frontmatter_test.go index d6e6e79c3..9d9b7c3b8 100644 --- a/parser/frontmatter_test.go +++ b/parser/frontmatter_test.go @@ -15,55 +15,55 @@ package parser import ( "bytes" - "fmt" "reflect" - "strings" "testing" + + "github.com/gohugoio/hugo/parser/metadecoders" ) func TestInterfaceToConfig(t *testing.T) { cases := []struct { - input interface{} - mark byte - want []byte - isErr bool + input interface{} + format metadecoders.Format + want []byte + isErr bool }{ // TOML - {map[string]interface{}{}, TOMLLead[0], nil, false}, + {map[string]interface{}{}, metadecoders.TOML, nil, false}, { map[string]interface{}{"title": "test 1"}, - TOMLLead[0], + metadecoders.TOML, []byte("title = \"test 1\"\n"), false, }, // YAML - {map[string]interface{}{}, YAMLLead[0], []byte("{}\n"), false}, + {map[string]interface{}{}, metadecoders.YAML, []byte("{}\n"), false}, { map[string]interface{}{"title": "test 1"}, - YAMLLead[0], + metadecoders.YAML, []byte("title: test 1\n"), false, }, // JSON - {map[string]interface{}{}, JSONLead[0], []byte("{}\n"), false}, + {map[string]interface{}{}, metadecoders.JSON, []byte("{}\n"), false}, { map[string]interface{}{"title": "test 1"}, - JSONLead[0], + metadecoders.JSON, []byte("{\n \"title\": \"test 1\"\n}\n"), false, }, // Errors - {nil, TOMLLead[0], nil, true}, - {map[string]interface{}{}, '$', nil, true}, + {nil, metadecoders.TOML, nil, true}, + {map[string]interface{}{}, "foo", nil, true}, } for i, c := range cases { var buf bytes.Buffer - err := InterfaceToConfig(c.input, rune(c.mark), &buf) + err := InterfaceToConfig(c.input, c.format, &buf) if err != nil { if c.isErr { continue @@ -76,179 +76,3 @@ func TestInterfaceToConfig(t *testing.T) { } } } - -func TestInterfaceToFrontMatter(t *testing.T) { - cases := []struct { - input interface{} - mark rune - want []byte - isErr bool - }{ - // TOML - {map[string]interface{}{}, '+', []byte("+++\n\n+++\n"), false}, - { - map[string]interface{}{"title": "test 1"}, - '+', - []byte("+++\ntitle = \"test 1\"\n\n+++\n"), - false, - }, - - // YAML - {map[string]interface{}{}, '-', []byte("---\n{}\n---\n"), false}, // - { - map[string]interface{}{"title": "test 1"}, - '-', - []byte("---\ntitle: test 1\n---\n"), - false, - }, - - // JSON - {map[string]interface{}{}, '{', []byte("{}\n"), false}, - { - map[string]interface{}{"title": "test 1"}, - '{', - []byte("{\n \"title\": \"test 1\"\n}\n"), - false, - }, - - // Errors - {nil, '+', nil, true}, - {map[string]interface{}{}, '$', nil, true}, - } - - for i, c := range cases { - var buf bytes.Buffer - err := InterfaceToFrontMatter(c.input, c.mark, &buf) - if err != nil { - if c.isErr { - continue - } - t.Fatalf("[%d] unexpected error value: %v", i, err) - } - - if !reflect.DeepEqual(buf.Bytes(), c.want) { - t.Errorf("[%d] not equal:\nwant %q,\n got %q", i, c.want, buf.Bytes()) - } - } -} - -func TestFormatToLeadRune(t *testing.T) { - for i, this := range []struct { - kind string - expect rune - }{ - {"yaml", '-'}, - {"yml", '-'}, - {"toml", '+'}, - {"tml", '+'}, - {"json", '{'}, - {"js", '{'}, - {"org", '#'}, - {"unknown", '+'}, - } { - result := FormatToLeadRune(this.kind) - - if result != this.expect { - t.Errorf("[%d] got %q but expected %q", i, result, this.expect) - } - } -} - -func TestRemoveTOMLIdentifier(t *testing.T) { - cases := []struct { - input string - want string - }{ - {"a = 1", "a = 1"}, - {"a = 1\r\n", "a = 1\r\n"}, - {"+++\r\na = 1\r\n+++\r\n", "a = 1\r\n"}, - {"+++\na = 1\n+++\n", "a = 1\n"}, - {"+++\nb = \"+++ oops +++\"\n+++\n", "b = \"+++ oops +++\"\n"}, - {"+++\nc = \"\"\"+++\noops\n+++\n\"\"\"\"\n+++\n", "c = \"\"\"+++\noops\n+++\n\"\"\"\"\n"}, - {"+++\nd = 1\n+++", "d = 1\n"}, - } - - for i, c := range cases { - res := removeTOMLIdentifier([]byte(c.input)) - if string(res) != c.want { - t.Errorf("[%d] given %q\nwant: %q\n got: %q", i, c.input, c.want, res) - } - } -} - -func BenchmarkFrontmatterTags(b *testing.B) { - - for _, frontmatter := range []string{"JSON", "YAML", "YAML2", "TOML"} { - for i := 1; i < 60; i += 20 { - doBenchmarkFrontmatter(b, frontmatter, i) - } - } -} - -func doBenchmarkFrontmatter(b *testing.B, fileformat string, numTags int) { - yamlTemplate := `--- -name: "Tags" -tags: -%s ---- -` - - yaml2Template := `--- -name: "Tags" -tags: %s ---- -` - tomlTemplate := `+++ -name = "Tags" -tags = %s -+++ -` - - jsonTemplate := `{ - "name": "Tags", - "tags": [ - %s - ] -}` - name := fmt.Sprintf("%s:%d", fileformat, numTags) - b.Run(name, func(b *testing.B) { - tags := make([]string, numTags) - var ( - tagsStr string - frontmatterTemplate string - ) - for i := 0; i < numTags; i++ { - tags[i] = fmt.Sprintf("Hugo %d", i+1) - } - if fileformat == "TOML" { - frontmatterTemplate = tomlTemplate - tagsStr = strings.Replace(fmt.Sprintf("%q", tags), " ", ", ", -1) - } else if fileformat == "JSON" { - frontmatterTemplate = jsonTemplate - tagsStr = strings.Replace(fmt.Sprintf("%q", tags), " ", ", ", -1) - } else if fileformat == "YAML2" { - frontmatterTemplate = yaml2Template - tagsStr = strings.Replace(fmt.Sprintf("%q", tags), " ", ", ", -1) - } else { - frontmatterTemplate = yamlTemplate - for _, tag := range tags { - tagsStr += "\n- " + tag - } - } - - frontmatter := fmt.Sprintf(frontmatterTemplate, tagsStr) - - p := page{frontmatter: []byte(frontmatter)} - - b.ResetTimer() - for i := 0; i < b.N; i++ { - meta, err := p.Metadata() - if err != nil { - b.Fatal(err) - } - if meta == nil { - b.Fatal("Meta is nil") - } - } - }) -} diff --git a/parser/long_text_test.md b/parser/long_text_test.md deleted file mode 100644 index e0cac502c..000000000 --- a/parser/long_text_test.md +++ /dev/null @@ -1,263 +0,0 @@ ---- -title: The Git Book - Long Text ---- -# Getting Started # - -This chapter will be about getting started with Git. We will begin at the beginning by explaining some background on version control tools, then move on to how to get Git running on your system and finally how to get it setup to start working with. At the end of this chapter you should understand why Git is around, why you should use it and you should be all setup to do so. - -## About Version Control ## - -What is version control, and why should you care? Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. Even though the examples in this book show software source code as the files under version control, in reality any type of file on a computer can be placed under version control. - -If you are a graphic or web designer and want to keep every version of an image or layout (which you certainly would), it is very wise to use a Version Control System (VCS). A VCS allows you to: revert files back to a previous state, revert the entire project back to a previous state, review changes made over time, see who last modified something that might be causing a problem, who introduced an issue and when, and more. Using a VCS also means that if you screw things up or lose files, you can generally recover easily. In addition, you get all this for very little overhead. - -### Local Version Control Systems ### - -Many people’s version-control method of choice is to copy files into another directory (perhaps a time-stamped directory, if they’re clever). This approach is very common because it is so simple, but it is also incredibly error prone. It is easy to forget which directory you’re in and accidentally write to the wrong file or copy over files you don’t mean to. - -To deal with this issue, programmers long ago developed local VCSs that had a simple database that kept all the changes to files under revision control (see Figure 1-1). - -Insert 18333fig0101.png -Figure 1-1. Local version control diagram. - -One of the more popular VCS tools was a system called rcs, which is still distributed with many computers today. Even the popular Mac OS X operating system includes the rcs command when you install the Developer Tools. This tool basically works by keeping patch sets (that is, the differences between files) from one revision to another in a special format on disk; it can then recreate what any file looked like at any point in time by adding up all the patches. - -### Centralized Version Control Systems ### - -The next major issue that people encounter is that they need to collaborate with developers on other systems. To deal with this problem, Centralized Version Control Systems (CVCSs) were developed. These systems, such as CVS, Subversion, and Perforce, have a single server that contains all the versioned files, and a number of clients that check out files from that central place. For many years, this has been the standard for version control (see Figure 1-2). - -Insert 18333fig0102.png -Figure 1-2. Centralized version control diagram. - -This setup offers many advantages, especially over local VCSs. For example, everyone knows to a certain degree what everyone else on the project is doing. Administrators have fine-grained control over who can do what; and it’s far easier to administer a CVCS than it is to deal with local databases on every client. - -However, this setup also has some serious downsides. The most obvious is the single point of failure that the centralized server represents. If that server goes down for an hour, then during that hour nobody can collaborate at all or save versioned changes to anything they’re working on. If the hard disk the central database is on becomes corrupted, and proper backups haven’t been kept, you lose absolutely everything—the entire history of the project except whatever single snapshots people happen to have on their local machines. Local VCS systems suffer from this same problem—whenever you have the entire history of the project in a single place, you risk losing everything. - -### Distributed Version Control Systems ### - -This is where Distributed Version Control Systems (DVCSs) step in. In a DVCS (such as Git, Mercurial, Bazaar or Darcs), clients don’t just check out the latest snapshot of the files: they fully mirror the repository. Thus if any server dies, and these systems were collaborating via it, any of the client repositories can be copied back up to the server to restore it. Every checkout is really a full backup of all the data (see Figure 1-3). - -Insert 18333fig0103.png -Figure 1-3. Distributed version control diagram. - -Furthermore, many of these systems deal pretty well with having several remote repositories they can work with, so you can collaborate with different groups of people in different ways simultaneously within the same project. This allows you to set up several types of workflows that aren’t possible in centralized systems, such as hierarchical models. - -## A Short History of Git ## - -As with many great things in life, Git began with a bit of creative destruction and fiery controversy. The Linux kernel is an open source software project of fairly large scope. For most of the lifetime of the Linux kernel maintenance (1991–2002), changes to the software were passed around as patches and archived files. In 2002, the Linux kernel project began using a proprietary DVCS system called BitKeeper. - -In 2005, the relationship between the community that developed the Linux kernel and the commercial company that developed BitKeeper broke down, and the tool’s free-of-charge status was revoked. This prompted the Linux development community (and in particular Linus Torvalds, |