Browse code

Merge branch 'dm-plugin-new-ChangesDirs' of https://github.com/alexlarsson/docker into alexlarsson-dm-plugin-new-ChangesDirs

Conflicts:
archive/changes.go

Michael Crosby authored on 2013/11/15 05:23:01
Showing 1 changed files
... ...
@@ -106,105 +106,178 @@ func Changes(layers []string, rw string) ([]Change, error) {
106 106
 	return changes, nil
107 107
 }
108 108
 
109
-func ChangesDirs(newDir, oldDir string) ([]Change, error) {
110
-	var changes []Change
111
-	err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error {
112
-		if err != nil {
113
-			return err
114
-		}
109
+type FileInfo struct {
110
+	parent   *FileInfo
111
+	name     string
112
+	stat     syscall.Stat_t
113
+	children map[string]*FileInfo
114
+}
115 115
 
116
-		var newStat syscall.Stat_t
117
-		err = syscall.Lstat(newPath, &newStat)
118
-		if err != nil {
119
-			return err
120
-		}
116
+func (root *FileInfo) LookUp(path string) *FileInfo {
117
+	parent := root
118
+	if path == "/" {
119
+		return root
120
+	}
121 121
 
122
-		// Rebase path
123
-		relPath, err := filepath.Rel(newDir, newPath)
124
-		if err != nil {
125
-			return err
122
+	pathElements := strings.Split(path, "/")
123
+	for _, elem := range pathElements {
124
+		if elem != "" {
125
+			child := parent.children[elem]
126
+			if child == nil {
127
+				return nil
128
+			}
129
+			parent = child
126 130
 		}
127
-		relPath = filepath.Join("/", relPath)
131
+	}
132
+	return parent
133
+}
128 134
 
129
-		// Skip root
130
-		if relPath == "/" || relPath == "/.docker-id" {
131
-			return nil
132
-		}
135
+func (info *FileInfo) path() string {
136
+	if info.parent == nil {
137
+		return "/"
138
+	}
139
+	return filepath.Join(info.parent.path(), info.name)
140
+}
133 141
 
142
+func (info *FileInfo) isDir() bool {
143
+	return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
144
+}
145
+
146
+func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
147
+	if oldInfo == nil {
148
+		// add
134 149
 		change := Change{
135
-			Path: relPath,
150
+			Path: info.path(),
151
+			Kind: ChangeAdd,
136 152
 		}
153
+		*changes = append(*changes, change)
154
+	}
137 155
 
138
-		oldPath := filepath.Join(oldDir, relPath)
139
-
140
-		var oldStat = &syscall.Stat_t{}
141
-		err = syscall.Lstat(oldPath, oldStat)
142
-		if err != nil {
143
-			if !os.IsNotExist(err) {
144
-				return err
145
-			}
146
-			oldStat = nil
156
+	// We make a copy so we can modify it to detect additions
157
+	// also, we only recurse on the old dir if the new info is a directory
158
+	// otherwise any previous delete/change is considered recursive
159
+	oldChildren := make(map[string]*FileInfo)
160
+	if oldInfo != nil && info.isDir() {
161
+		for k, v := range oldInfo.children {
162
+			oldChildren[k] = v
147 163
 		}
164
+	}
148 165
 
149
-		if oldStat == nil {
150
-			change.Kind = ChangeAdd
151
-			changes = append(changes, change)
152
-		} else {
153
-			if oldStat.Ino != newStat.Ino ||
154
-				oldStat.Mode != newStat.Mode ||
166
+	for name, newChild := range info.children {
167
+		oldChild, _ := oldChildren[name]
168
+		if oldChild != nil {
169
+			// change?
170
+			oldStat := &oldChild.stat
171
+			newStat := &newChild.stat
172
+			// Note: We can't compare inode or ctime or blocksize here, because these change
173
+			// when copying a file into a container. However, that is not generally a problem
174
+			// because any content change will change mtime, and any status change should
175
+			// be visible when actually comparing the stat fields. The only time this
176
+			// breaks down is if some code intentionally hides a change by setting
177
+			// back mtime
178
+			if oldStat.Mode != newStat.Mode ||
155 179
 				oldStat.Uid != newStat.Uid ||
156 180
 				oldStat.Gid != newStat.Gid ||
157 181
 				oldStat.Rdev != newStat.Rdev ||
158
-				oldStat.Size != newStat.Size ||
159
-				oldStat.Blocks != newStat.Blocks ||
160
-				oldStat.Mtim != newStat.Mtim ||
161
-				oldStat.Ctim != newStat.Ctim {
162
-				change.Kind = ChangeModify
163
-				changes = append(changes, change)
182
+				// Don't look at size for dirs, its not a good measure of change
183
+				(oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) ||
184
+				oldStat.Mtim != newStat.Mtim {
185
+				change := Change{
186
+					Path: newChild.path(),
187
+					Kind: ChangeModify,
188
+				}
189
+				*changes = append(*changes, change)
164 190
 			}
191
+
192
+			// Remove from copy so we can detect deletions
193
+			delete(oldChildren, name)
165 194
 		}
166 195
 
167
-		return nil
168
-	})
169
-	if err != nil {
170
-		return nil, err
196
+		newChild.addChanges(oldChild, changes)
197
+	}
198
+	for _, oldChild := range oldChildren {
199
+		// delete
200
+		change := Change{
201
+			Path: oldChild.path(),
202
+			Kind: ChangeDelete,
203
+		}
204
+		*changes = append(*changes, change)
171 205
 	}
172
-	err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error {
206
+
207
+}
208
+
209
+func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
210
+	var changes []Change
211
+
212
+	info.addChanges(oldInfo, &changes)
213
+
214
+	return changes
215
+}
216
+
217
+func newRootFileInfo() *FileInfo {
218
+	root := &FileInfo{
219
+		name:     "/",
220
+		children: make(map[string]*FileInfo),
221
+	}
222
+	return root
223
+}
224
+
225
+func collectFileInfo(sourceDir string) (*FileInfo, error) {
226
+	root := newRootFileInfo()
227
+
228
+	err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
173 229
 		if err != nil {
174 230
 			return err
175 231
 		}
176 232
 
177 233
 		// Rebase path
178
-		relPath, err := filepath.Rel(oldDir, oldPath)
234
+		relPath, err := filepath.Rel(sourceDir, path)
179 235
 		if err != nil {
180 236
 			return err
181 237
 		}
182 238
 		relPath = filepath.Join("/", relPath)
183 239
 
184
-		// Skip root
185 240
 		if relPath == "/" {
186 241
 			return nil
187 242
 		}
188 243
 
189
-		change := Change{
190
-			Path: relPath,
244
+		parent := root.LookUp(filepath.Dir(relPath))
245
+		if parent == nil {
246
+			return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
191 247
 		}
192 248
 
193
-		newPath := filepath.Join(newDir, relPath)
249
+		info := &FileInfo{
250
+			name:     filepath.Base(relPath),
251
+			children: make(map[string]*FileInfo),
252
+			parent:   parent,
253
+		}
194 254
 
195
-		var newStat = &syscall.Stat_t{}
196
-		err = syscall.Lstat(newPath, newStat)
197
-		if err != nil && os.IsNotExist(err) {
198
-			change.Kind = ChangeDelete
199
-			changes = append(changes, change)
255
+		if err := syscall.Lstat(path, &info.stat); err != nil {
256
+			return err
200 257
 		}
201 258
 
259
+		parent.children[info.name] = info
260
+
202 261
 		return nil
203 262
 	})
204 263
 	if err != nil {
205 264
 		return nil, err
206 265
 	}
207
-	return changes, nil
266
+	return root, nil
267
+}
268
+
269
+// Compare two directories and generate an array of Change objects describing the changes
270
+func ChangesDirs(newDir, oldDir string) ([]Change, error) {
271
+	oldRoot, err := collectFileInfo(oldDir)
272
+	if err != nil {
273
+		return nil, err
274
+	}
275
+	newRoot, err := collectFileInfo(newDir)
276
+	if err != nil {
277
+		return nil, err
278
+	}
279
+
280
+	return newRoot.Changes(oldRoot), nil
208 281
 }
209 282
 
210 283
 func ExportChanges(root, rw string) (Archive, error) {