func (d *deltaeval) Copy(repo *libgit.Repository, src ObjectReference, offset, length uint64) { id, _ := libgit.NewId(src) _, _, r, err := repo.GetRawObject(id, false) if err != nil { panic(err) } defer r.Close() if offset > 0 { tmp := make([]byte, offset) n, err := io.ReadFull(r, tmp) if err != nil { panic(err) } if n == 0 || uint64(n) != offset { panic("Couldn't correctly read offset.") } } reading := make([]byte, length) n, err := io.ReadFull(r, reading) if uint64(n) != length || err != nil { panic("Error copying data") } d.value = append(d.value, reading...) }
func Branch(repo *libgit.Repository, args []string) { switch len(args) { case 0: branches, err := repo.GetBranches() if err != nil { fmt.Fprintln(os.Stderr, "Could not get list of branches.") return } head := getHeadBranch(repo) for _, b := range branches { if head == b { fmt.Print("* ") } else { fmt.Print(" ") } fmt.Println(b) } case 1: if head, err := getHeadId(repo); err == nil { if cerr := libgit.CreateBranch(repo.Path, args[0], head); cerr != nil { fmt.Fprintf(os.Stderr, "Could not create branch: %s\n", cerr.Error()) } } else { fmt.Fprintf(os.Stderr, "Could not create branch: %s\n", err.Error()) } default: fmt.Fprintln(os.Stderr, "Usage: go-git branch [branchname]") } }
func writeObject(repo *libgit.Repository, objType string, rawdata []byte) error { obj := []byte(fmt.Sprintf("%s %d\000", objType, len(rawdata))) obj = append(obj, rawdata...) sha := sha1.Sum(obj) if have, _, err := repo.HaveObject(fmt.Sprintf("%x", sha)); have == true || err != nil { if err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err) return err } //fmt.Fprintf(os.Stderr, "Already have object %x\n", sha) return ObjectExists } directory := fmt.Sprintf("%x", sha[0:1]) file := fmt.Sprintf("%x", sha[1:]) fmt.Printf("Putting %x in %s/%s\n", sha, directory, file) os.MkdirAll(repo.Path+"/objects/"+directory, os.FileMode(0755)) f, err := os.Create(repo.Path + "/objects/" + directory + "/" + file) if err != nil { return err } defer f.Close() w := zlib.NewWriter(f) if _, err := w.Write(obj); err != nil { return err } defer w.Close() return nil }
func getTreeishId(repo *libgit.Repository, treeish string) string { if branchId, err := repo.GetCommitIdOfBranch(treeish); err == nil { return branchId } if len(treeish) == 40 { return treeish } panic("TODO: Didn't implement getTreeishId") }
func Init(repo *libgit.Repository, args []string) { if len(args) > 0 { if dir := args[len(args)-1]; dir != "init" { err := os.MkdirAll(dir, 0755) if err != nil { panic("Couldn't create directory for initializing git.") } err = os.Chdir(dir) if err != nil { panic("Couldn't change working directory while initializing git.") } if repo != nil { repo.Path = ".git/" } } } // These are all the directories created by a clean "git init" // with the canonical git implementation os.Mkdir(".git", 0755) os.MkdirAll(".git/objects/pack", 0755) os.MkdirAll(".git/objects/info", 0755) os.MkdirAll(".git/info", 0755) // Should have exclude file in it os.MkdirAll(".git/hooks", 0755) // should have sample hooks in it. os.MkdirAll(".git/branches", 0755) os.MkdirAll(".git/refs/heads", 0755) os.MkdirAll(".git/refs/tags", 0755) ioutil.WriteFile(".git/HEAD", []byte("ref: refs/heads/master\n"), 0644) ioutil.WriteFile(".git/config", []byte("[core]\n\trepositoryformatversion = 0\n\tbare = false\n"), 0644) ioutil.WriteFile(".git/description", []byte("Unnamed repository; edit this file 'description' to name the repository.\n"), 0644) }
// Calculate the final reslt of a REF_DELTA. This is largely // based on the delta algorithm as described at: // http://stefan.saasen.me/articles/git-clone-in-haskell-from-the-bottom-up/#pack_file_format func calculateDelta(repo *libgit.Repository, reference ObjectReference, delta []byte) (PackEntryType, []byte) { deltaStream := bytes.NewBuffer(delta) // Read 2 variable length strings for the source and target buffer // length // read the source length, but we don't care. We just want to advance // the deltaStream pointer the proper amount. ReadVariable(deltaStream) // Read the target length so we know when we've finished processing // the delta stream. targetLength := ReadVariable(deltaStream) d := deltaeval{} for { d.DoInstruction(repo, deltaStream, reference, targetLength) if targetLength == uint64(len(d.value)) { break } if len(d.value) > int(targetLength) { panic("Read too much data from delta stream") } } // GetRawObject to find the underlying type of the original // reference id, err := libgit.NewId(reference) if err != nil { panic(err) } objt, _, _, err := repo.GetRawObject(id, true) if err != nil { panic(err) } switch objt { case libgit.ObjectCommit: return OBJ_COMMIT, d.value case libgit.ObjectTree: return OBJ_TREE, d.value case libgit.ObjectBlob: return OBJ_BLOB, d.value case libgit.ObjectTag: return OBJ_TAG, d.value } panic("Unhandle object type while calculating delta.") return 0, nil }
func writeIndexEntries(repo *libgit.Repository, prefix string, entries []*GitIndexEntry) ([20]byte, error) { content := bytes.NewBuffer(nil) // [mode] [file/folder name]\0[SHA-1 of referencing blob or tree as [20]byte] lastname := "" firstIdxForTree := -1 for idx, obj := range entries { nameBits := strings.Split(obj.PathName, "/") // Either it's the last entry and we haven't written a tree yet, or it's not the last // entry but the directory changed if (nameBits[0] != lastname || idx == len(entries)-1) && lastname != "" { var islice []*GitIndexEntry if idx == len(entries)-1 { islice = entries[firstIdxForTree:] } else { islice = entries[firstIdxForTree:idx] } subsha1, err := writeIndexSubtree(repo, lastname, islice) if err != nil { panic(err) } // Write the object fmt.Fprintf(content, "%o %s\x00", 0040000, lastname) content.Write(subsha1[:]) // Reset the data keeping track of what this tree is. lastname = "" firstIdxForTree = -1 } if len(nameBits) == 1 { //write the blob for the file portion fmt.Fprintf(content, "%o %s\x00", obj.Mode, obj.PathName) content.Write(obj.Sha1[:]) lastname = "" } else { lastname = nameBits[0] if firstIdxForTree == -1 { firstIdxForTree = idx } } } sha1, err := repo.StoreObjectLoose(libgit.ObjectTree, bytes.NewReader(content.Bytes())) return [20]byte(sha1), err }
func resetIndexFromCommit(repo *libgit.Repository, commitId string) error { // If the index doesn't exist, idx is a new index, so ignore // the path error that ReadIndex is returning idx, _ := ReadIndex(repo) com, err := repo.GetCommit(commitId) if err != nil { fmt.Printf("%s\n", err) return err } treeId := com.TreeId() tree := libgit.NewTree(repo, treeId) if tree == nil { panic("Error retriving tree for commit") } idx.ResetIndex(repo, tree) writeIndex(repo, idx, "index") return nil }
func Merge(repo *libgit.Repository, args []string) { if len(args) != 1 { fmt.Fprintf(os.Stderr, "Usage: go-git merge branchname\n") return } commit, err := repo.GetCommitOfBranch(args[0]) if err != nil { fmt.Fprintf(os.Stderr, "Invalid branch: %s\n", args[0]) return } head, err := getHeadId(repo) if err != nil { panic(err) } // The current branch is an ancestor of HEAD. This // is a fast-forward. if headCom, err := repo.GetCommit(head); err == nil { if isAncestor(headCom, fmt.Sprintf("%s", commit.Id)) { fmt.Fprintf(os.Stderr, "Already up-to-date.\n") return } } // Head is an ancestor of the current branch. if isAncestor(commit, head) { hb := getHeadBranch(repo) newId := fmt.Sprintf("%s", commit.Id) ioutil.WriteFile(".git/refs/heads/"+hb, []byte(newId), 0644) resetIndexFromCommit(repo, newId) fmt.Printf("Hooray! A Fast forward on %s! New should should be %s\n", hb, commit.Id) return } panic("Only fast forward commits are currently supported.") }
func Log(repo *libgit.Repository, args []string) error { if len(args) != 0 { fmt.Fprintf(os.Stderr, "Usage: go-git log\nNo options are currently supported.\n") return errors.New("No options are currently supported for log") } head, err := getHeadId(repo) if err != nil { return err } // CommitsBefore also returns the commit passed.. l, err := repo.CommitsBefore(head) if err != nil { return err } for e := l.Front(); e != nil; e = e.Next() { c, ok := e.Value.(*libgit.Commit) if ok { printCommit(c) } } return nil }
func CommitTree(repo *libgit.Repository, args []string) string { content := bytes.NewBuffer(nil) var parents []string var messageString, messageFile string var skipNext bool var tree string for idx, val := range args { if idx == 0 && val[0] != '-' { tree = val continue } if skipNext == true { skipNext = false continue } switch val { case "-p": parents = append(parents, args[idx+1]) skipNext = true case "-m": messageString += "\n" + args[idx+1] + "\n" skipNext = true case "-F": messageFile = args[idx+1] skipNext = true } } if messageString == "" { if messageFile != "" { // TODO: READ commit message from messageFile here } else { // If neither messageString nor messageFile are set, read // from STDIN } panic("Must provide message with -m parameter to commit-tree") } if tree == "" { tree = args[len(args)-1] } // TODO: Validate tree id fmt.Fprintf(content, "tree %s\n", tree) for _, val := range parents { fmt.Fprintf(content, "parent %s\n", val) } author := getAuthor(repo) t := time.Now() _, tzoff := t.Zone() // for some reason t.Zone() returns the timezone offset in seconds // instead of hours, so convert it to an hour format string tzStr := fmt.Sprintf("%+03d00", tzoff/(60*60)) fmt.Fprintf(content, "author %s %d %s\n", author, t.Unix(), tzStr) fmt.Fprintf(content, "committer %s %d %s\n", author, t.Unix(), tzStr) fmt.Fprintf(content, "%s", messageString) fmt.Printf("%s", content.Bytes()) sha1, err := repo.StoreObjectLoose(libgit.ObjectCommit, bytes.NewReader(content.Bytes())) if err != nil { return "" } return fmt.Sprintf("%s", sha1) }
func getHeadId(repo *libgit.Repository) (string, error) { if headBranch := getHeadBranch(repo); headBranch != "" { return repo.GetCommitIdOfBranch(getHeadBranch(repo)) } return "", InvalidHead }
// Adds a file to the index, without writing it to disk. // To write it to disk after calling this, use GitIndex.WriteIndex // // This will do the following: // write git object blob of file contents to .git/objects // normalize os.File name to path relative to gitRoot // search GitIndex for normalized name // if GitIndexEntry found // update GitIndexEntry to point to the new object blob // else // add new GitIndexEntry if not found // func (g *GitIndex) AddFile(repo *libgit.Repository, file *os.File) { sha1, err := repo.StoreObjectLoose(libgit.ObjectBlob, file) if err != nil { fmt.Fprintf(os.Stderr, "Error storing object: %s", err) } fmt.Printf("Sha1: %s\n", sha1) fmt.Printf("Name is %s\n", file.Name()) name := getNormalizedName(file) for _, entry := range g.Objects { if entry.PathName == name { entry.Sha1 = sha1 fstat, err := file.Stat() if err != nil { panic(err) } modTime := fstat.ModTime() entry.Mtime = uint32(modTime.Unix()) entry.Mtimenano = uint32(modTime.Nanosecond()) entry.Fsize = uint32(fstat.Size()) return } } fstat, err := file.Stat() if err != nil { panic(err) } modTime := fstat.ModTime() stat := fstat.Sys().(*syscall.Stat_t) csec, cnano := stat.Ctim.Unix() var mode uint32 if fstat.IsDir() { // This should really recursively call add for each file in the directory. panic("Add can't handle directories. yet.") } else { // if it's a regular file, just assume it's 0644 for now mode = 0x81A4 } // mode is /* 32-bit mode, split into (high to low bits) 4-bit object type valid values in binary are 1000 (regular file), 1010 (symbolic link) and 1110 (gitlink) 3-bit unused 9-bit unix permission. Only 0755 and 0644 are valid for regular files. Symbolic links and gitlinks have value 0 in this field. Flags is A 16-bit 'flags' field split into (high to low bits) 1-bit assume-valid flag 1-bit extended flag (must be zero in version 2) 2-bit stage (during merge) 12-bit name length if the length is less than 0xFFF; otherwise 0xFFF is stored in this field. */ //var flags uint16 = 0x8000 // start with "assume-valid" flag var flags uint16 = 0x8000 // start with "assume-valid" flag if len(name) >= 0x0FFF { flags |= 0x0FFF } else { flags |= (uint16(len(name)) & 0x0FFF) } g.Objects = append(g.Objects, &GitIndexEntry{ fixedIndexEntry{ uint32(csec), // right? uint32(cnano), // right? uint32(modTime.Unix()), // right uint32(modTime.Nanosecond()), // right uint32(stat.Dev), // right uint32(stat.Ino), // right mode, stat.Uid, // right? stat.Gid, // right? uint32(fstat.Size()), // right sha1, // this is right flags, // right }, name, }) g.NumberIndexEntries += 1 sort.Sort(ByPath(g.Objects)) return }