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  }