github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/updateindex.go (about)

     1  package git
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  )
     9  
    10  // A BitSetter denotes an option for a flag which sets or unsets
    11  // a bit. It determines both whether the bit should be set, and
    12  // the value it should be set to.
    13  type BitSetter struct {
    14  	// If true, the bit should be modified
    15  	Modify bool
    16  
    17  	// Value to set the bit to if Modify is true.
    18  	Value bool
    19  }
    20  
    21  type CacheInfo struct {
    22  	Mode EntryMode
    23  	Sha1 Sha1
    24  	Path IndexPath
    25  }
    26  
    27  type UpdateIndexOptions struct {
    28  	Add, Remove            bool
    29  	Refresh, ReallyRefresh bool
    30  	Quiet                  bool
    31  	IgnoreSubmodules       bool
    32  
    33  	IndexVersion int
    34  
    35  	// Read the index information from IndexInfo
    36  	IndexInfo io.Reader
    37  
    38  	Unmerged      bool
    39  	IgnoreMissing bool
    40  
    41  	Again           bool
    42  	Unresolve       bool
    43  	InfoOnly        bool
    44  	ForceRemove     bool
    45  	Replace         bool
    46  	Verbose         bool
    47  	NullTerminate   bool
    48  	Chmod           BitSetter
    49  	SplitIndex      BitSetter
    50  	UntrackedCache  BitSetter
    51  	SkipWorktree    BitSetter
    52  	AssumeUnchanged BitSetter
    53  
    54  	CacheInfo CacheInfo
    55  
    56  	Stdin io.Reader
    57  
    58  	// With the official git client "git add -v" saysing "remove 'foo'"
    59  	// while "git update-index --verbose" says "add 'foo'" when removing
    60  	// a file. This is a hack so that we can have the same behaviour without
    61  	// having to duplicate code.
    62  	correctRemoveMsg bool
    63  }
    64  
    65  // This implements the git update-index command. It updates the index
    66  // passed as a parameter, and returns it. It does *not* write it to
    67  // disk, that's the responsibility of the caller.
    68  func UpdateIndex(c *Client, idx *Index, opts UpdateIndexOptions, files []File) (*Index, error) {
    69  	if opts.IndexInfo != nil {
    70  		return UpdateIndexFromReader(c, opts, opts.IndexInfo)
    71  	}
    72  	if opts.Refresh {
    73  		return UpdateIndexRefresh(c, idx, opts)
    74  	}
    75  	if opts.CacheInfo != (CacheInfo{}) {
    76  		return UpdateIndexCacheInfo(c, idx, opts)
    77  	}
    78  	for _, file := range files {
    79  		ipath, err := file.IndexPath(c)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  		if file.IsDir() {
    84  			if opts.Remove || opts.ForceRemove {
    85  				entries, err := LsFiles(c, LsFilesOptions{Cached: true}, []File{file})
    86  				if err != nil {
    87  					return nil, err
    88  				}
    89  				for _, i := range entries {
    90  					idx.RemoveFile(i.PathName)
    91  				}
    92  				continue
    93  			} else if opts.Add {
    94  				return nil, fmt.Errorf("%v is a directory - add files inside instead", file)
    95  			}
    96  		}
    97  		if !file.Exists() || opts.ForceRemove {
    98  			if opts.Remove || opts.ForceRemove {
    99  				idx.RemoveFile(ipath)
   100  				if opts.Verbose {
   101  					if opts.correctRemoveMsg {
   102  						fmt.Printf("remove '%v'\n", file.String())
   103  					} else {
   104  						fmt.Printf("add '%v'\n", file.String())
   105  					}
   106  				}
   107  			} else {
   108  				return nil, fmt.Errorf("%v does not exist and --remove not passed", file)
   109  			}
   110  		} else if err := idx.AddFile(c, file, opts); err != nil {
   111  			if !opts.Add {
   112  				// This is making invalid assumptions that the only
   113  				// thing that might go wrong is that createEntry was
   114  				// false and the file isn't in the index
   115  				return nil, fmt.Errorf("Can not add %v to index. Missing --add?", file)
   116  			}
   117  			// If add was true and something went else went wrong,
   118  			// return the error
   119  			return nil, err
   120  		}
   121  		if opts.Verbose {
   122  			fmt.Printf("add '%v'\n", file)
   123  		}
   124  		if opts.SkipWorktree.Modify {
   125  			idx.SetSkipWorktree(c, ipath, opts.SkipWorktree.Value)
   126  		}
   127  	}
   128  	return idx, nil
   129  }
   130  
   131  func UpdateIndexFromReader(c *Client, opts UpdateIndexOptions, r io.Reader) (*Index, error) {
   132  	idx := NewIndex()
   133  	scanner := bufio.NewScanner(r)
   134  	for scanner.Scan() {
   135  		line := strings.TrimSpace(scanner.Text())
   136  		if line == "" {
   137  			continue
   138  		}
   139  		tab := strings.Split(line, "\t")
   140  		if len(tab) != 2 {
   141  			return nil, fmt.Errorf("Invalid line in index-info: %v", line)
   142  		}
   143  		path := tab[1]
   144  		spaces := strings.Split(tab[0], " ")
   145  		switch len(spaces) {
   146  		case 2:
   147  			// mode SP sha1 TAB path
   148  			sha1, err := Sha1FromString(spaces[1])
   149  			if err != nil {
   150  				return nil, err
   151  			}
   152  			if spaces[0] == "0" {
   153  				// Mode "0" means remove index
   154  				idx.RemoveFile(IndexPath(path))
   155  			} else {
   156  				mode, err := ModeFromString(spaces[0])
   157  				if err != nil {
   158  					return nil, err
   159  				}
   160  				if err := idx.AddStage(c, IndexPath(path), mode, sha1, Stage0, 0, 0, UpdateIndexOptions{Add: true}); err != nil {
   161  					return nil, err
   162  				}
   163  			}
   164  		case 3:
   165  			switch len(spaces[1]) {
   166  			case 40:
   167  				// mode SP sha1 SP stage TAB path
   168  				mode, err := ModeFromString(spaces[0])
   169  				if err != nil {
   170  					return nil, err
   171  				}
   172  				sha1, err := Sha1FromString(spaces[1])
   173  				if err != nil {
   174  					return nil, err
   175  				}
   176  				var stage Stage
   177  				switch spaces[2] {
   178  				case "0":
   179  					stage = Stage0
   180  				case "1":
   181  					stage = Stage1
   182  				case "2":
   183  					stage = Stage2
   184  				case "3":
   185  					stage = Stage3
   186  				default:
   187  					return nil, fmt.Errorf("Invalid stage: %v", spaces[2])
   188  				}
   189  				if err := idx.AddStage(c, IndexPath(path), mode, sha1, stage, 0, 0, UpdateIndexOptions{Add: true}); err != nil {
   190  					return nil, err
   191  				}
   192  			default:
   193  				// mode SP type SP sha1 TAB path
   194  				mode, err := ModeFromString(spaces[0])
   195  				if err != nil {
   196  					return nil, err
   197  				}
   198  				if mode.TreeType() != spaces[1] {
   199  					return nil, fmt.Errorf("Mode does not match type: %v", line)
   200  				}
   201  				sha1, err := Sha1FromString(spaces[2])
   202  				if err != nil {
   203  					return nil, err
   204  				}
   205  				if err := idx.AddStage(c, IndexPath(path), mode, sha1, Stage0, 0, 0, UpdateIndexOptions{Add: true}); err != nil {
   206  					return nil, err
   207  				}
   208  			}
   209  		default:
   210  			return nil, fmt.Errorf("Invalid line in index-info: %v", line)
   211  		}
   212  	}
   213  	if err := scanner.Err(); err != nil {
   214  		return nil, err
   215  	}
   216  	return idx, nil
   217  }
   218  
   219  func UpdateIndexRefresh(c *Client, idx *Index, opts UpdateIndexOptions) (*Index, error) {
   220  	for _, entry := range idx.Objects {
   221  		f, err := entry.PathName.FilePath(c)
   222  		if err != nil {
   223  			return nil, err
   224  		}
   225  		if !f.Exists() {
   226  			// Nothing to refresh
   227  			continue
   228  		}
   229  		if err := entry.RefreshStat(c); err != nil {
   230  			return nil, err
   231  		}
   232  	}
   233  	return idx, nil
   234  }
   235  
   236  func UpdateIndexCacheInfo(c *Client, idx *Index, opts UpdateIndexOptions) (*Index, error) {
   237  	err := idx.AddStage(c, opts.CacheInfo.Path, opts.CacheInfo.Mode, opts.CacheInfo.Sha1, Stage0, 0, 0, opts)
   238  	return idx, err
   239  }