github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/reset.go (about) 1 package git 2 3 import ( 4 "fmt" 5 ) 6 7 type ResetOptions struct { 8 Quiet bool 9 10 Soft, Mixed, Hard, Merge, Keep bool 11 } 12 13 // Reset implementes the "git reset" command and delegates to the appropriate 14 // ResetMode or ResetUnstage subcommand. 15 // 16 // While the third option is a []File slice, the first argument may instead 17 // be a "filename" which parses to a commitish or treeish by rev-parse, in 18 // which case it's used as the base to reset to, not a file. In the event that 19 // there's both a commitish in git and filename of the same name, it's treated 20 // as a commit and you must instead pass "HEAD" as the first file to treat 21 // it as a filename. 22 // 23 // This behaviour is different than the official git client, which provides 24 // an error and a message to disambiguate based on the presence of "--" on 25 // the command line. (This is not an option here, because this is a library 26 // function, not a command line.) 27 func Reset(c *Client, opts ResetOptions, files []File) error { 28 if opts.Soft && len(files) > 1 { 29 return fmt.Errorf("Cannot do soft reset with paths") 30 } 31 if opts.Mixed { 32 switch { 33 case len(files) > 1: 34 goto unstage 35 case len(files) == 1: 36 cmt, err := RevParseCommitish(c, &RevParseOptions{}, files[0].String()) 37 if err != nil { 38 goto unstage 39 } 40 return ResetMode(c, opts, cmt) 41 case len(files) == 0: 42 cmt, err := c.GetHeadCommit() 43 if err != nil { 44 return err 45 } 46 return ResetMode(c, opts, cmt) 47 } 48 } 49 if opts.Hard && len(files) > 1 { 50 return fmt.Errorf("Cannot do hard reset with paths") 51 } 52 if opts.Merge && len(files) > 1 { 53 return fmt.Errorf("Cannot do merge reset with paths") 54 } 55 if opts.Keep && len(files) > 1 { 56 return fmt.Errorf("Cannot do keep reset with paths") 57 } 58 if opts.Soft || opts.Mixed || opts.Hard || opts.Merge || opts.Keep { 59 if len(files) == 0 { 60 head, err := c.GetHeadCommit() 61 if err != nil { 62 return err 63 } 64 return ResetMode(c, opts, head) 65 } else if len(files) == 1 { 66 cmt, err := RevParseCommitish(c, &RevParseOptions{}, files[0].String()) 67 if err != nil { 68 return err 69 } 70 return ResetMode(c, opts, cmt) 71 } 72 // > 1 was handled above. 73 panic("This line should be unreachable.") 74 75 } 76 unstage: 77 if len(files) == 0 { 78 head, err := c.GetHeadCommit() 79 if err != nil { 80 return err 81 } 82 return ResetUnstage(c, opts, head, files) 83 } 84 treeish, err := RevParseTreeish(c, &RevParseOptions{}, files[0].String()) 85 if err != nil { 86 treeish, err = c.GetHeadCommit() 87 if err != nil { 88 return err 89 } 90 } else { 91 files = files[1:] 92 } 93 return ResetUnstage(c, opts, treeish, files) 94 } 95 96 // ResetMode implements "git reset [--soft | --mixed | --hard | --merge | --keep]" <commit> 97 func ResetMode(c *Client, opts ResetOptions, cmt Commitish) error { 98 if !opts.Soft && !opts.Mixed && !opts.Hard && !opts.Merge && !opts.Keep { 99 // The default mode is mixed if none were specified. 100 opts.Mixed = true 101 } 102 103 // Update HEAD to cmt 104 // If soft -- nothing else 105 // if mixed -- read-tree cmt 106 // if hard -- read-tree cmt && checkout-index -f -all 107 // if merge | keep-- read man page more carefully, for now return an error 108 109 if opts.Merge { 110 return fmt.Errorf("ResetMode --merge Not implemented") 111 } 112 if opts.Keep { 113 return fmt.Errorf("ResetMode --keep Not implemented") 114 } 115 116 comm, err := cmt.CommitID(c) 117 if err != nil { 118 return err 119 } 120 if err := UpdateRef(c, UpdateRefOptions{}, "HEAD", comm, fmt.Sprintf("reset: moving to %v", comm)); err != nil { 121 return err 122 } 123 if opts.Mixed || opts.Hard { 124 idx, err := ReadTree(c, ReadTreeOptions{Reset: true, Update: true}, comm) 125 if err != nil { 126 return err 127 } 128 if opts.Hard { 129 if err := CheckoutIndexUncommited(c, idx, CheckoutIndexOptions{All: true, Force: true, UpdateStat: true}, nil); err != nil { 130 return err 131 } 132 } 133 } 134 return nil 135 } 136 137 // ResetUnstage implements "git reset [<treeish>] -- paths 138 func ResetUnstage(c *Client, opts ResetOptions, tree Treeish, files []File) error { 139 index, _ := c.GitDir.ReadIndex() 140 diffs, err := DiffIndex(c, DiffIndexOptions{Cached: true}, index, tree, files) 141 if err != nil { 142 return err 143 } 144 for _, entry := range diffs { 145 f, err := entry.Name.FilePath(c) 146 if err != nil { 147 return err 148 } 149 mtime, err := f.MTime() 150 if err != nil { 151 return err 152 } 153 if entry.Src == (TreeEntry{}) { 154 // The file wasn't in HEAD. Remove it from the index, 155 // but only do it for files that were explicitly removed. 156 for _, unstage := range files { 157 if f == unstage { 158 index.RemoveFile(entry.Name) 159 } 160 } 161 } else if err := index.AddStage(c, entry.Name, entry.Src.FileMode, entry.Src.Sha1, Stage0, uint32(entry.SrcSize), mtime, UpdateIndexOptions{Add: true}); err != nil { 162 return err 163 } 164 } 165 166 f, err := c.GitDir.Create(File("index")) 167 if err != nil { 168 return err 169 } 170 defer f.Close() 171 if err := index.WriteIndex(f); err != nil { 172 return err 173 } 174 175 if !opts.Quiet { 176 newdiff, err := DiffFiles(c, DiffFilesOptions{}, nil) 177 if err != nil { 178 return err 179 } 180 if len(newdiff) > 0 { 181 fmt.Printf("Unstaged changes after reset:\n") 182 } 183 for _, file := range newdiff { 184 var status string 185 if file.Dst == (TreeEntry{}) { 186 status = "D" 187 } else { 188 status = "M" 189 } 190 fmt.Printf("%v\t%v\n", status, file.Name) 191 } 192 193 } 194 return nil 195 }